From 67b3318584881a6062ba8d098bbfc186981ec3be Mon Sep 17 00:00:00 2001 From: krzychu124 Date: Fri, 24 May 2019 20:24:05 +0200 Subject: [PATCH 001/142] Added 'Release LABS' build configuration and LABS preprocessor instruction - Fixes #326 --- TLM/TLM/TLM.csproj | 10 ++++++++++ TLM/TLM/TrafficManagerMod.cs | 10 ++++++---- TLM/TMPE.sln | 26 ++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 7cb327aa8..739a64d89 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -67,6 +67,16 @@ prompt ..\TMPE.ruleset + + bin\Release LABS\ + PF2;QUEUEDSTATS;PARKINGAI;SPEEDLIMITS;ROUTING;JUNCTIONRESTRICTIONS;VEHICLERESTRICTIONS;ADVANCEDAI;CUSTOMTRAFFICLIGHTS;LABS + true + true + pdbonly + AnyCPU + prompt + ..\TMPE.ruleset + ..\dependencies\Assembly-CSharp.dll diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index bac416def..a6e365249 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -10,10 +10,12 @@ namespace TrafficManager { public class TrafficManagerMod : IUserMod { - - public static readonly string Version = "10.20"; - - public static readonly uint GameVersion = 184673552u; +#if LABS + public static readonly string Version = "10.20 LABS"; +#else + public static readonly string Version = "10.20"; +#endif + public static readonly uint GameVersion = 184673552u; public static readonly uint GameVersionA = 1u; public static readonly uint GameVersionB = 12u; public static readonly uint GameVersionC = 0u; diff --git a/TLM/TMPE.sln b/TLM/TMPE.sln index 005f07cc1..a8c3748f0 100644 --- a/TLM/TMPE.sln +++ b/TLM/TMPE.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.572 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TLM", "TLM\TLM.csproj", "{7422AE58-8B0A-401C-9404-F4A438EFFE10}" ProjectSection(ProjectDependencies) = postProject @@ -44,6 +44,7 @@ Global Debug|Any CPU = Debug|Any CPU FullDebug|Any CPU = FullDebug|Any CPU PF2_Debug|Any CPU = PF2_Debug|Any CPU + Release LABS|Any CPU = Release LABS|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution @@ -55,6 +56,8 @@ Global {7422AE58-8B0A-401C-9404-F4A438EFFE10}.FullDebug|Any CPU.Build.0 = FullDebug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release LABS|Any CPU.ActiveCfg = Release LABS|Any CPU + {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release LABS|Any CPU.Build.0 = Release LABS|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.ActiveCfg = Release|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.Build.0 = Release|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU @@ -65,6 +68,8 @@ Global {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release LABS|Any CPU.Build.0 = Release|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.ActiveCfg = Release|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.Build.0 = Release|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU @@ -75,6 +80,8 @@ Global {D0D1848A-9BAE-4121-89A0-66757D16BC73}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Release LABS|Any CPU.Build.0 = Release|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Release|Any CPU.Build.0 = Release|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU @@ -85,6 +92,8 @@ Global {663B991F-32A1-46E1-A4D3-540F8EA7F386}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Release LABS|Any CPU.Build.0 = Release|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Release|Any CPU.ActiveCfg = Release|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Release|Any CPU.Build.0 = Release|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU @@ -95,6 +104,8 @@ Global {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Release LABS|Any CPU.Build.0 = Release|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Release|Any CPU.Build.0 = Release|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU @@ -105,6 +116,8 @@ Global {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Release LABS|Any CPU.Build.0 = Release|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Release|Any CPU.ActiveCfg = Release|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Release|Any CPU.Build.0 = Release|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU @@ -115,6 +128,8 @@ Global {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Release LABS|Any CPU.Build.0 = Release|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Release|Any CPU.Build.0 = Release|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU @@ -125,6 +140,8 @@ Global {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Release LABS|Any CPU.Build.0 = Release|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Release|Any CPU.Build.0 = Release|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU @@ -135,10 +152,15 @@ Global {F8759084-DF5B-4A54-B73C-824640A8FA3F}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU + {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU + {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Release LABS|Any CPU.Build.0 = Release|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {48E6CCE9-FF8E-4D57-96D2-09BD83C6088F} + EndGlobalSection EndGlobal From 95f51aed5b0489f69c77f9ba94b3cdd5b706292f Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 28 May 2019 02:22:51 +0100 Subject: [PATCH 002/142] Update TrafficManagerMod.cs Separated out the branch string, so we can now distinguish between DEBUG, LABS and STABLE. Also minor code rearranging to aid readability. --- TLM/TLM/TrafficManagerMod.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index a6e365249..c43a47d63 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -10,21 +10,27 @@ namespace TrafficManager { public class TrafficManagerMod : IUserMod { + + public string Version => "10.20"; + #if LABS - public static readonly string Version = "10.20 LABS"; + public string Branch => "LABS"; +#elif DEBUG + public string Branch => "DEBUG"; #else - public static readonly string Version = "10.20"; + public string Branch => "STABLE"; #endif + + public string Name => "TM:PE " + Version + " " + Branch; + + public string Description => "Manage your city's traffic"; + public static readonly uint GameVersion = 184673552u; public static readonly uint GameVersionA = 1u; public static readonly uint GameVersionB = 12u; public static readonly uint GameVersionC = 0u; public static readonly uint GameVersionBuild = 5u; - public string Name => "TM:PE " + Version; - - public string Description => "Manage your city's traffic"; - public void OnEnabled() { Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); if (UIView.GetAView() != null) { From 2e92cfbf0f1bc9ec0eabb811f82a8460edaed03b Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 28 May 2019 02:32:31 +0100 Subject: [PATCH 003/142] Update ModsCompatibilityChecker.cs Using the new LABS token allows us to dynamically add other version of TM:PE (either STABLE or LABS as applicalbe) to the incompatible mod checker. This still won't detect local install versions, but it will drastically reduce number of users who have more than one TM:PE subbed and subsequently reduce the issues with non-saving road config. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index df1b5e65a..10e29541e 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -36,6 +36,19 @@ public void PerformModCheck() { } } + // Based on the build, also warn about other TM:PE builds if present + // + // Note: Workshop published versions still won't detect local installs but at + // least it will reduce number of users havin two workshop versions subscribed +#if LABS + incompatibleMods.Add(583429740 , "TM:PE STABLE"); +#elif DEBUG + incompatibleMods.Add(1637663252, "TM:PE LABS"); + incompatibleMods.Add(583429740 , "TM:PE STABLE"); +#else // STABLE + incompatibleMods.Add(1637663252, "TM:PE LABS"); +#endif + if (incompatibleMods.Count > 0) { Log.Warning("Incompatible mods detected! Count: " + incompatibleMods.Count); From cecbeb60e90b276bcb473c1650a94875b5fa78bd Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 28 May 2019 03:44:35 +0100 Subject: [PATCH 004/142] Made version static again to fix Menu UI VersionLabel.cs references the `Version` value, so made it static like it was before (not ideal, but not a regression either). Also added L, D, or S to denote build branch which will be useful for support purposes. --- TLM/TLM/TrafficManagerMod.cs | 3 ++- TLM/TLM/UI/MainMenu/VersionLabel.cs | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index c43a47d63..c1d4c0052 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -11,7 +11,8 @@ namespace TrafficManager { public class TrafficManagerMod : IUserMod { - public string Version => "10.20"; + // Note: `Version` is also used in UI/MainMenu/VersionLabel.cs + public static readonly string Version = "10.20"; #if LABS public string Branch => "LABS"; diff --git a/TLM/TLM/UI/MainMenu/VersionLabel.cs b/TLM/TLM/UI/MainMenu/VersionLabel.cs index 4434266e6..3ec56c3e9 100644 --- a/TLM/TLM/UI/MainMenu/VersionLabel.cs +++ b/TLM/TLM/UI/MainMenu/VersionLabel.cs @@ -8,10 +8,19 @@ namespace TrafficManager.UI.MainMenu { public class VersionLabel : UILabel { public override void Start() { - size = new Vector2(MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH, MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER); // TODO use current size profile - text = "TM:PE " + TrafficManagerMod.Version; - relativePosition = new Vector3(5f, 5f); - textAlignment = UIHorizontalAlignment.Left; + + size = new Vector2(MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH, MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER); // TODO use current size profile + +#if LABS + text = "TM:PE " + TrafficManagerMod.Version + "-L"; +#elif DEBUG + text = "TM:PE " + TrafficManagerMod.Version + "-D"; +#else // STABLE + text = "TM:PE " + TrafficManagerMod.Version + "-S"; +#endif + relativePosition = new Vector3(5f, 5f); + + textAlignment = UIHorizontalAlignment.Left; } } } From b4b737ce68c03fe186f122ceff66dd5c4a8298b3 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 28 May 2019 04:37:14 +0100 Subject: [PATCH 005/142] Update ModsCompatibilityChecker.cs Fixed blunder with location of the additions to incompatible mods list. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 10e29541e..8787d107f 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -36,19 +36,6 @@ public void PerformModCheck() { } } - // Based on the build, also warn about other TM:PE builds if present - // - // Note: Workshop published versions still won't detect local installs but at - // least it will reduce number of users havin two workshop versions subscribed -#if LABS - incompatibleMods.Add(583429740 , "TM:PE STABLE"); -#elif DEBUG - incompatibleMods.Add(1637663252, "TM:PE LABS"); - incompatibleMods.Add(583429740 , "TM:PE STABLE"); -#else // STABLE - incompatibleMods.Add(1637663252, "TM:PE LABS"); -#endif - if (incompatibleMods.Count > 0) { Log.Warning("Incompatible mods detected! Count: " + incompatibleMods.Count); @@ -81,6 +68,19 @@ private Dictionary LoadIncompatibleModList() { } } + // Treat any other branches of TM:PE as incompatible (causes issues with saving road customisations). + // This still won't detect local builds, but will reduce support workload from end users who have + // multiple TM:PE subscribed. + // See also: https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues/211 +#if LABS + incompatibleMods.Add(583429740 , "TM:PE STABLE"); +#elif DEBUG // assume local build + incompatibleMods.Add(1637663252, "TM:PE LABS"); + incompatibleMods.Add(583429740 , "TM:PE STABLE"); +#else // STABLE + incompatibleMods.Add(1637663252, "TM:PE LABS"); +#endif + return incompatibleMods; } From f58990c4f158ed7bf222ed4edc877434bdd5654d Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 28 May 2019 04:46:57 +0100 Subject: [PATCH 006/142] Specify TMPE branch on incompatible mod checker dialog This adds the branch to the title on the incompatible mods dialog. I've updated language files as applicable; most were untranslated, except `fr` and `ko` - `ko` didn't include the word `Traffic Manager` and `fr` had the translation at the start so easy to modify. --- TLM/TLM/Resources/lang.txt | 2 +- TLM/TLM/Resources/lang_de.txt | 2 +- TLM/TLM/Resources/lang_es.txt | 2 +- TLM/TLM/Resources/lang_fr.txt | 2 +- TLM/TLM/Resources/lang_it.txt | 2 +- TLM/TLM/Resources/lang_ja.txt | 2 +- TLM/TLM/Resources/lang_ko.txt | 2 +- TLM/TLM/Resources/lang_nl.txt | 2 +- TLM/TLM/Resources/lang_pl.txt | 2 +- TLM/TLM/Resources/lang_pt.txt | 2 +- TLM/TLM/Resources/lang_ru.txt | 2 +- TLM/TLM/Resources/lang_zh-tw.txt | 2 +- TLM/TLM/UI/IncompatibleModsPanel.cs | 9 ++++++++- 13 files changed, 20 insertions(+), 13 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 3cec98f37..43dbf6607 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Vehicles may turn at red traffic lights Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index da6304904..e46f5123b 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Fahrzeuge dürfen an roten Ampeln abbiegen Also_apply_to_left/right_turns_between_one-way_streets Gilt auch für Links- & Rechtskurven zwischen Einbahnstraßen Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 20e12f61e..56ce3bea1 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Los vehículos pueden girar en los semáforos rojos. Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index 54587ee59..fd08dfda0 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Les véhicules peuvent tourner aux feux rouges Also_apply_to_left/right_turns_between_one-way_streets Appliquer de même aux virages gauche/droite entre des rues à sens unique Scan_for_known_incompatible_mods_on_startup Rechercher les mods incompatibles au lancement Ignore_disabled_mods Ignorer les mods désactivés -Traffic_Manager_detected_incompatible_mods Le gestionnaire de trafic a détecté des mods incompatibles +Traffic_Manager_detected_incompatible_mods a détecté des mods incompatibles Notify_me_if_there_is_an_unexpected_mod_conflict Afficher un message d'erreur si un mod incompatible est détecté diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index 4de9a9c57..aaed10fd6 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red I veicoli possono girare ai semafori rossi Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index d5cd3835e..a8fda7bd7 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red 車両が赤信号で曲がることがある Also_apply_to_left/right_turns_between_one-way_streets 一方通行路の左右の曲がり角にも適用 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index d9681d7af..ada9b8e30 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red 빨간 신호등에서 차량이 진입 할 수 있습 Also_apply_to_left/right_turns_between_one-way_streets 빨간 신호등에서 우회전을 허용 Scan_for_known_incompatible_mods_on_startup 게임 실행시 비호환되는 모드 스캔 Ignore_disabled_mods 비활성화된 모드 무시하기 -Traffic_Manager_detected_incompatible_mods Traffic Manager와 비호환되는 모드 감지 +Traffic_Manager_detected_incompatible_mods 와 비호환되는 모드 감지 Notify_me_if_there_is_an_unexpected_mod_conflict 모드와 비 호환되는 모드 발견 시 에러 보여주기 diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 25d39bc05..03dd6284a 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Voertuigen kunnen bij rode verkeerslichten draaien Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index d2d2379a0..dc4bf6b9c 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Pojazdy mogą skręcić w prawo na czerwonym świetle Also_apply_to_left/right_turns_between_one-way_streets Uwzględnia również skręt w lewo/prawo pomiędzy drogami jednokierunkowymi Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie gry Ignore_disabled_mods Ignoruj wyłączone mody -Traffic_Manager_detected_incompatible_mods Traffic Manager wykrył niekompatybilne mody +Traffic_Manager_detected_incompatible_mods wykrył niekompatybilne mody Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index c981124b5..c2174f6b3 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Veículos podem virar nos semáforos vermelhos Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index c99fbcd0f..53e4aa025 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red Разрешить поворот направо на Also_apply_to_left/right_turns_between_one-way_streets Разрешить направо/налево между улицами с односторонним движением Scan_for_known_incompatible_mods_on_startup Сканирование известных несовместимых модов при запуске Ignore_disabled_mods Игнорировать отключённые моды -Traffic_Manager_detected_incompatible_mods Traffic Manager обнаружил несовместимые моды +Traffic_Manager_detected_incompatible_mods обнаружил несовместимые моды Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index 00a2eccfe..57d470201 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -230,5 +230,5 @@ Vehicles_may_turn_on_red 車輛可能會在紅色交通燈處轉彎 Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods +Traffic_Manager_detected_incompatible_mods detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index 9b20fb9e5..eee395bb4 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -50,7 +50,14 @@ public void Initialize() { title.autoSize = true; title.padding = new RectOffset(10, 10, 15, 15); title.relativePosition = new Vector2(60, 12); - title.text = Translation.GetString("Traffic_Manager_detected_incompatible_mods"); + +#if LABS + title.text = "TM:PE LABS " + Translation.GetString("Traffic_Manager_detected_incompatible_mods"); +#elif DEBUG + title.text = "TM:PE DEBUG " + Translation.GetString("Traffic_Manager_detected_incompatible_mods"); +#else // STABLE + title.text = "TM:PE STABLE " + Translation.GetString("Traffic_Manager_detected_incompatible_mods"); +#endif closeButton = mainPanel.AddUIComponent(); closeButton.eventClick += CloseButtonClick; From 1eabb10a344e232a3ca440d2fb63aea24f52af1e Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 28 May 2019 05:09:10 +0100 Subject: [PATCH 007/142] Update ModsCompatibilityChecker.cs Changed workshop ids to ulong. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 8787d107f..87c8ffe40 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -73,12 +73,12 @@ private Dictionary LoadIncompatibleModList() { // multiple TM:PE subscribed. // See also: https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues/211 #if LABS - incompatibleMods.Add(583429740 , "TM:PE STABLE"); + incompatibleMods.Add(583429740u , "TM:PE STABLE"); #elif DEBUG // assume local build - incompatibleMods.Add(1637663252, "TM:PE LABS"); - incompatibleMods.Add(583429740 , "TM:PE STABLE"); + incompatibleMods.Add(1637663252u, "TM:PE LABS"); + incompatibleMods.Add(583429740u , "TM:PE STABLE"); #else // STABLE - incompatibleMods.Add(1637663252, "TM:PE LABS"); + incompatibleMods.Add(1637663252u, "TM:PE LABS"); #endif return incompatibleMods; From 63982a602a7aab1d6c1f875c7bf1de52faa970b9 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Wed, 29 May 2019 02:31:30 +0100 Subject: [PATCH 008/142] Update VersionLabel.cs Updated to full string, not abbreviation. --- TLM/TLM/UI/MainMenu/VersionLabel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TLM/TLM/UI/MainMenu/VersionLabel.cs b/TLM/TLM/UI/MainMenu/VersionLabel.cs index 3ec56c3e9..fc0ce7970 100644 --- a/TLM/TLM/UI/MainMenu/VersionLabel.cs +++ b/TLM/TLM/UI/MainMenu/VersionLabel.cs @@ -12,11 +12,11 @@ public override void Start() { size = new Vector2(MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH, MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER); // TODO use current size profile #if LABS - text = "TM:PE " + TrafficManagerMod.Version + "-L"; + text = "TM:PE " + TrafficManagerMod.Version + " LABS"; #elif DEBUG - text = "TM:PE " + TrafficManagerMod.Version + "-D"; + text = "TM:PE " + TrafficManagerMod.Version + " DEBUG"; #else // STABLE - text = "TM:PE " + TrafficManagerMod.Version + "-S"; + text = "TM:PE " + TrafficManagerMod.Version + " STABLE"; #endif relativePosition = new Vector3(5f, 5f); From d4afd0f128fa5d286adcd5b90914c32de1816459 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Wed, 29 May 2019 04:05:37 +0100 Subject: [PATCH 009/142] Partial implementation for detect and delete of local TMPE build * Local TMPE build now detected * UI shows "Delete" button for it * Click handler knows difference, but doesn't yet delete * Directive block is commented out for testing * Needs additional work for completion! --- TLM/TLM/UI/IncompatibleModsPanel.cs | 91 +++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index eee395bb4..57ef50170 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -6,10 +6,14 @@ using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; +using ICities; using UnityEngine; +using static ColossalFramework.Plugins.PluginManager; namespace TrafficManager.UI { public class IncompatibleModsPanel : UIPanel { + private const ulong LOCAL_TMPE = 0u; + private UILabel title; private UIButton closeButton; private UISprite warningIcon; @@ -79,9 +83,10 @@ public void Initialize() { scrollablePanel.size = new Vector2(550, 340); scrollablePanel.relativePosition = new Vector3(0, 0); scrollablePanel.clipChildren = true; + + int acc = 0; + UIPanel item; if (IncompatibleMods.Count != 0) { - int acc = 0; - UIPanel item; IncompatibleMods.ForEach((pair) => { item = CreateEntry(ref scrollablePanel, pair.Value, pair.Key); item.relativePosition = new Vector2(0, acc); @@ -91,6 +96,17 @@ public void Initialize() { item = null; } +// directive block commented out for testing +// #if !DEBUG + if (GetLocalMod("TM:PE") != null) { + Log._Debug("Local build of TM:PE found"); + item = CreateEntry(ref scrollablePanel, "TM:PE LOCAL BUILD", LOCAL_TMPE); + item.relativePosition = new Vector2(0, acc); + item.size = new Vector2(560, 50); + item = null; + } +// #endif + scrollablePanel.FitTo(panel); scrollablePanel.scrollWheelDirection = UIOrientation.Vertical; scrollablePanel.builtinKeyNavigation = true; @@ -118,7 +134,6 @@ public void Initialize() { thumb.relativePosition = Vector3.zero; verticalScroll.thumbObject = thumb; - blurEffect = GameObject.Find("ModalEffect").GetComponent(); AttachUIComponent(blurEffect.gameObject); blurEffect.size = new Vector2(resolution.x, resolution.y); @@ -153,21 +168,39 @@ private UIPanel CreateEntry(ref UIScrollablePanel parent, string name, ulong ste label.text = name; label.textAlignment = UIHorizontalAlignment.Left; label.relativePosition = new Vector2(10, 15); - CreateButton(panel, "Unsubscribe", (int) panel.width - 170, 10, delegate(UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); + if (steamId == LOCAL_TMPE) { // local TM:PE needs deleting + CreateButton(panel, "Delete", (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); + } else { // workshop mod needs unsubscribing + CreateButton(panel, "Unsubscribe", (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); + } return panel; } private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, ulong steamId) { - Log.Info("Trying to unsubscribe workshop item " + steamId); - component.isEnabled = false; - if (PlatformService.workshop.Unsubscribe(new PublishedFileId(steamId))) { - IncompatibleMods.Remove(steamId); + + if (steamId == LOCAL_TMPE) { // Local TM:PE + + Log.Info("Trying to delete local build of TM:PE"); + component.isEnabled = false; + PluginInfo localTMPE = GetLocalMod("TM:PE"); + // TODO: Find out how to delete the local mod using its PluginInfo component.parent.Disable(); component.isVisible = false; - Log.Info("Workshop item " + steamId + " unsubscribed"); - } else { - Log.Warning("Failed unsubscribing workshop item " + steamId); - component.isEnabled = true; + + } else { // Workshop mod + + Log.Info("Trying to unsubscribe workshop item " + steamId); + component.isEnabled = false; + if (PlatformService.workshop.Unsubscribe(new PublishedFileId(steamId))) { + IncompatibleMods.Remove(steamId); + component.parent.Disable(); + component.isVisible = false; + Log.Info("Workshop item " + steamId + " unsubscribed"); + } else { + Log.Warning("Failed unsubscribing workshop item " + steamId); + component.isEnabled = true; + } + } } @@ -238,5 +271,39 @@ private void TryPopModal() { ValueAnimator.Animate("ModalEffect", delegate(float val) { blurEffect.opacity = val; }, new AnimatedFloat(1f, 0f, 0.7f, EasingType.CubicEaseOut), delegate() { blurEffect.Hide(); }); } } + + // get plugin info for a local mod + // note: may be null if mod not found + private PluginInfo GetLocalMod(string name) + { + try + { + foreach (PluginInfo plugin in Singleton.instance.GetPluginsInfo()) + { + if (!plugin.isBuiltin && !plugin.isCameraScript && plugin.publishedFileID.AsUInt64 == ulong.MaxValue && GetModName(plugin).Contains(name)) + { + return plugin; + } + } + } + catch (Exception e) + { + Debug.Log("[TM:PE] ModsCompatibilityChecker.GetLocalMod() ERROR:"); + Debug.LogException(e); + } + return null; + } + + // returns name of mod as defined in the IUserMod class of that mod + private string GetModName(PluginInfo pluginInfo) + { + string name = pluginInfo.name; + IUserMod[] instances = pluginInfo.GetInstances(); + if ((int)instances.Length > 0) + { + name = instances[0].Name; + } + return name; + } } } \ No newline at end of file From b9e57ac067c24e7386274389f2e094b23eddf0c1 Mon Sep 17 00:00:00 2001 From: Victor-Philipp Negoescu Date: Sat, 1 Jun 2019 00:33:48 +0200 Subject: [PATCH 010/142] #225: Regular vehicles do not change lanes at toll booths --- TLM/TLM/Manager/Impl/RoutingManager.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/TLM/TLM/Manager/Impl/RoutingManager.cs b/TLM/TLM/Manager/Impl/RoutingManager.cs index a4fc04c40..c2eb9b880 100644 --- a/TLM/TLM/Manager/Impl/RoutingManager.cs +++ b/TLM/TLM/Manager/Impl/RoutingManager.cs @@ -310,8 +310,6 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui return; } - bool prevIsMergeLane = Constants.ServiceFactory.NetService.CheckLaneFlags(laneId, NetLane.Flags.Merge); - NetInfo prevSegmentInfo = null; bool prevSegIsInverted = false; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort prevSegId, ref NetSegment segment) { @@ -361,14 +359,24 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui bool nextIsTransition = false; bool nextIsEndOrOneWayOut = false; bool nextHasTrafficLights = false; + ushort buildingId = 0; Constants.ServiceFactory.NetService.ProcessNode(nextNodeId, delegate (ushort nodeId, ref NetNode node) { nextIsJunction = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; nextIsTransition = (node.m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; nextHasTrafficLights = (node.m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; nextIsEndOrOneWayOut = (node.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; + buildingId = NetNode.FindOwnerBuilding(nextNodeId, 32f); return true; }); + bool isTollBooth = false; + if (buildingId != 0) { + Constants.ServiceFactory.BuildingService.ProcessBuilding(buildingId, (ushort bId, ref Building building) => { + isTollBooth = building.Info.m_buildingAI is TollBoothAI; + return true; + }); + } + bool nextIsSimpleJunction = false; bool nextIsSplitJunction = false; if (Options.highwayRules && !nextHasTrafficLights) { @@ -393,6 +401,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSegIsInverted={prevSegIsInverted} leftHandDrive={leftHandDrive}"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSimilarLaneCount={prevSimilarLaneCount} prevInnerSimilarLaneIndex={prevInnerSimilarLaneIndex} prevOuterSimilarLaneIndex={prevOuterSimilarLaneIndex} prevHasBusLane={prevHasBusLane}"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextIsJunction={nextIsJunction} nextIsEndOrOneWayOut={nextIsEndOrOneWayOut} nextHasTrafficLights={nextHasTrafficLights} nextIsSimpleJunction={nextIsSimpleJunction} nextIsSplitJunction={nextIsSplitJunction} isNextRealJunction={isNextRealJunction}"); + Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextNodeId={nextNodeId} buildingId={buildingId} isTollBooth={isTollBooth}"); } #endif @@ -559,15 +568,15 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui } } - if (prevIsMergeLane && Constants.ServiceFactory.NetService.CheckLaneFlags(nextLaneId, NetLane.Flags.Merge)) { + if (isTollBooth) { #if DEBUGROUTING if (debugFine) - Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is a merge lane, as the previous lane. adding as Default."); + Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextNodeId={nextNodeId}, buildingId={buildingId} is a toll booth. Preventing lane changes."); #endif if (nextOuterSimilarLaneIndex == prevOuterSimilarLaneIndex) { #if DEBUGROUTING if (debugFine) - Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is a continuous merge lane. adding as Default."); + Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is associated with a toll booth (buildingId={buildingId}). adding as Default."); #endif isCompatibleLane = true; transitionType = LaneEndTransitionType.Default; From 4891a210aa3b4ee59fe88495ec1c49b983595c80 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 3 Jun 2019 13:44:01 +0100 Subject: [PATCH 011/142] Update IncompatibleModsPanel.cs Added local mod deletion + code cleanup, still testing --- TLM/TLM/UI/IncompatibleModsPanel.cs | 78 ++++++++++++++++------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index 57ef50170..a2e1eaf42 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using ColossalFramework; using ColossalFramework.Globalization; +using ColossalFramework.IO; using ColossalFramework.PlatformServices; using ColossalFramework.Plugins; using ColossalFramework.UI; @@ -84,6 +85,13 @@ public void Initialize() { scrollablePanel.relativePosition = new Vector3(0, 0); scrollablePanel.clipChildren = true; +#if !DEBUG + if (GetLocalTMPE() != null) { + Log._Debug("Local build of TM:PE found - adding to IncompatbileMods list"); + IncompatibleMods.Add(LOCAL_TMPE, "TM:PE LOCAL BUILD"); + } +#endif + int acc = 0; UIPanel item; if (IncompatibleMods.Count != 0) { @@ -96,17 +104,6 @@ public void Initialize() { item = null; } -// directive block commented out for testing -// #if !DEBUG - if (GetLocalMod("TM:PE") != null) { - Log._Debug("Local build of TM:PE found"); - item = CreateEntry(ref scrollablePanel, "TM:PE LOCAL BUILD", LOCAL_TMPE); - item.relativePosition = new Vector2(0, acc); - item.size = new Vector2(560, 50); - item = null; - } -// #endif - scrollablePanel.FitTo(panel); scrollablePanel.scrollWheelDirection = UIOrientation.Vertical; scrollablePanel.builtinKeyNavigation = true; @@ -178,29 +175,26 @@ private UIPanel CreateEntry(ref UIScrollablePanel parent, string name, ulong ste private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, ulong steamId) { - if (steamId == LOCAL_TMPE) { // Local TM:PE + bool success = false; - Log.Info("Trying to delete local build of TM:PE"); - component.isEnabled = false; - PluginInfo localTMPE = GetLocalMod("TM:PE"); - // TODO: Find out how to delete the local mod using its PluginInfo - component.parent.Disable(); - component.isVisible = false; - - } else { // Workshop mod + component.isEnabled = false; + if (steamId == LOCAL_TMPE) { + Log.Info("Trying to delete local build of TM:PE"); + success = DeleteLocalTMPE(); + } else { Log.Info("Trying to unsubscribe workshop item " + steamId); - component.isEnabled = false; - if (PlatformService.workshop.Unsubscribe(new PublishedFileId(steamId))) { - IncompatibleMods.Remove(steamId); - component.parent.Disable(); - component.isVisible = false; - Log.Info("Workshop item " + steamId + " unsubscribed"); - } else { - Log.Warning("Failed unsubscribing workshop item " + steamId); - component.isEnabled = true; - } + success = PlatformService.workshop.Unsubscribe(new PublishedFileId(steamId)); + } + if (success) { + IncompatibleMods.Remove(steamId); + component.parent.Disable(); + component.isVisible = false; + Log.Info("Successfully removed incompatbile mod"); + } else { + Log.Warning("Failed to remove incompatible mod"); + component.isEnabled = true; } } @@ -272,15 +266,29 @@ private void TryPopModal() { } } - // get plugin info for a local mod - // note: may be null if mod not found - private PluginInfo GetLocalMod(string name) + private bool DeleteLocalTMPE() + { + PluginInfo localTMPE = GetLocalTMPE(); + try + { + localTMPE.Unload(); + DirectoryUtils.DeleteDirectory(localTMPE.modPath); + return true; + } + catch (Exception e) + { + Log.Warning("Failed to delete local TM:PE from " + localTMPE.modPath); + return false; + } + } + + private PluginInfo GetLocalTMPE() { try { foreach (PluginInfo plugin in Singleton.instance.GetPluginsInfo()) { - if (!plugin.isBuiltin && !plugin.isCameraScript && plugin.publishedFileID.AsUInt64 == ulong.MaxValue && GetModName(plugin).Contains(name)) + if (!plugin.isBuiltin && !plugin.isCameraScript && plugin.publishedFileID.AsUInt64 == ulong.MaxValue && GetModName(plugin).Contains("TM:PE")) { return plugin; } @@ -288,7 +296,7 @@ private PluginInfo GetLocalMod(string name) } catch (Exception e) { - Debug.Log("[TM:PE] ModsCompatibilityChecker.GetLocalMod() ERROR:"); + Log.Warning("ModsCompatibilityChecker.GetLocalTMPE() error - see game log for details"); Debug.LogException(e); } return null; From 959fe78120233078a74eded352d891ab73039849 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 3 Jun 2019 16:02:31 +0100 Subject: [PATCH 012/142] Correctly detect and delete local TMPE builds Moved detection to correct fine, now detects properly even if no other incompatible mods present. Also tested unload/delete which seems to be working fine. --- TLM/TLM/UI/IncompatibleModsPanel.cs | 11 ++---- TLM/TLM/Util/ModsCompatibilityChecker.cs | 43 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index a2e1eaf42..d3b989d07 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -85,13 +85,6 @@ public void Initialize() { scrollablePanel.relativePosition = new Vector3(0, 0); scrollablePanel.clipChildren = true; -#if !DEBUG - if (GetLocalTMPE() != null) { - Log._Debug("Local build of TM:PE found - adding to IncompatbileMods list"); - IncompatibleMods.Add(LOCAL_TMPE, "TM:PE LOCAL BUILD"); - } -#endif - int acc = 0; UIPanel item; if (IncompatibleMods.Count != 0) { @@ -271,7 +264,9 @@ private bool DeleteLocalTMPE() PluginInfo localTMPE = GetLocalTMPE(); try { + Log._Debug("Attempt to Unload local TM:PE"); localTMPE.Unload(); + Log._Debug("Attempt to delete local TM:PE folder " + localTMPE.modPath); DirectoryUtils.DeleteDirectory(localTMPE.modPath); return true; } @@ -296,7 +291,7 @@ private PluginInfo GetLocalTMPE() } catch (Exception e) { - Log.Warning("ModsCompatibilityChecker.GetLocalTMPE() error - see game log for details"); + Log.Warning("IncompatibleModsWarning.GetLocalTMPE() error - see game log for details"); Debug.LogException(e); } return null; diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 87c8ffe40..f419da89f 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -9,12 +9,15 @@ using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; +using ICities; using TrafficManager.UI; using UnityEngine; +using static ColossalFramework.Plugins.PluginManager; namespace TrafficManager.Util { public class ModsCompatibilityChecker { //TODO include %APPDATA% mods folder + private const ulong LOCAL_TMPE = 0u; private const string RESOURCES_PREFIX = "TrafficManager.Resources."; private const string DEFAULT_INCOMPATIBLE_MODS_FILENAME = "incompatible_mods.txt"; @@ -36,6 +39,13 @@ public void PerformModCheck() { } } +#if !DEBUG + if (HasLocalTMPE()) + { + incompatibleMods.Add(LOCAL_TMPE, "TM:PE Local Build"); + } +#endif + if (incompatibleMods.Count > 0) { Log.Warning("Incompatible mods detected! Count: " + incompatibleMods.Count); @@ -91,5 +101,38 @@ private ulong[] GetUserModsList() { PublishedFileId[] ids = ContentManagerPanel.subscribedItemsTable.ToArray(); return ids.Select(id => id.AsUInt64).ToArray(); } + + private bool HasLocalTMPE() + { + try + { + foreach (PluginInfo plugin in Singleton.instance.GetPluginsInfo()) + { + // How to detect local mod: plugin.publishedFileID.AsUInt64 == ulong.MaxValue + if (!plugin.isBuiltin && !plugin.isCameraScript && plugin.publishedFileID.AsUInt64 == ulong.MaxValue && GetModName(plugin).Contains("TM:PE")) + { + return true; + } + } + } + catch (Exception e) + { + Log.Warning("ModsCompatibilityChecker.HasLocalTMPE() error - see game log for details"); + Debug.LogException(e); + } + return false; + } + + // returns name of mod as defined in the IUserMod class of that mod + private string GetModName(PluginInfo plugin) + { + string name = plugin.name; + IUserMod[] instances = plugin.GetInstances(); + if ((int)instances.Length > 0) + { + name = instances[0].Name; + } + return name; + } } } \ No newline at end of file From 72a92cbe9a62e14d24cde273592cefd45ff460c2 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 3 Jun 2019 16:09:23 +0100 Subject: [PATCH 013/142] Update IncompatibleModsPanel.cs Minor code cleanup --- TLM/TLM/UI/IncompatibleModsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index d3b989d07..4df8e65c1 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -85,9 +85,9 @@ public void Initialize() { scrollablePanel.relativePosition = new Vector3(0, 0); scrollablePanel.clipChildren = true; - int acc = 0; - UIPanel item; if (IncompatibleMods.Count != 0) { + int acc = 0; + UIPanel item; IncompatibleMods.ForEach((pair) => { item = CreateEntry(ref scrollablePanel, pair.Value, pair.Key); item.relativePosition = new Vector2(0, acc); From 48cd41db247b474d97ece08d0d8ec170b3882411 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 3 Jun 2019 16:27:13 +0100 Subject: [PATCH 014/142] Update IncompatibleModsPanel.cs Improved code readability --- TLM/TLM/UI/IncompatibleModsPanel.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index 4df8e65c1..f7400dc85 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -151,18 +151,19 @@ private void CloseButtonClick(UIComponent component, UIMouseEventParameter event } private UIPanel CreateEntry(ref UIScrollablePanel parent, string name, ulong steamId) { + string caption = steamId == LOCAL_TMPE ? "Delete" : "Unsubscribe"; + UIPanel panel = parent.AddUIComponent(); panel.size = new Vector2(560, 50); panel.backgroundSprite = "ContentManagerItemBackground"; + UILabel label = panel.AddUIComponent(); label.text = name; label.textAlignment = UIHorizontalAlignment.Left; label.relativePosition = new Vector2(10, 15); - if (steamId == LOCAL_TMPE) { // local TM:PE needs deleting - CreateButton(panel, "Delete", (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); - } else { // workshop mod needs unsubscribing - CreateButton(panel, "Unsubscribe", (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); - } + + CreateButton(panel, caption, (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); + return panel; } From a5d9caa656f093e2a8738b09f1947e4c90b81b03 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Wed, 5 Jun 2019 00:45:40 +0100 Subject: [PATCH 015/142] Refactored to allow detection of multiple local builds Tested and seems to work, although I can't figure out why the wrong folder name is shown for local builds. --- TLM/TLM/TrafficManagerMod.cs | 28 +-- TLM/TLM/UI/IncompatibleModsPanel.cs | 193 ++++++++++---------- TLM/TLM/Util/ModsCompatibilityChecker.cs | 217 +++++++++++++---------- 3 files changed, 226 insertions(+), 212 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index c1d4c0052..83d7921ba 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -11,6 +11,12 @@ namespace TrafficManager { public class TrafficManagerMod : IUserMod { + public static readonly uint GameVersion = 184673552u; + public static readonly uint GameVersionA = 1u; + public static readonly uint GameVersionB = 12u; + public static readonly uint GameVersionC = 0u; + public static readonly uint GameVersionBuild = 5u; + // Note: `Version` is also used in UI/MainMenu/VersionLabel.cs public static readonly string Version = "10.20"; @@ -26,31 +32,27 @@ public class TrafficManagerMod : IUserMod { public string Description => "Manage your city's traffic"; - public static readonly uint GameVersion = 184673552u; - public static readonly uint GameVersionA = 1u; - public static readonly uint GameVersionB = 12u; - public static readonly uint GameVersionC = 0u; - public static readonly uint GameVersionBuild = 5u; - public void OnEnabled() { - Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); - if (UIView.GetAView() != null) { - OnGameIntroLoaded(); - } else { - LoadingManager.instance.m_introLoaded += OnGameIntroLoaded; + Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} {Branch} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); + + // check for incompatible mods + if (UIView.GetAView() != null) { // when TM:PE is enabled in content manager + CheckForIncompatibleMods(); + } else { // or when game first loads if TM:PE was already enabled + LoadingManager.instance.m_introLoaded += CheckForIncompatibleMods; } } public void OnDisabled() { Log.Info("TM:PE disabled."); - LoadingManager.instance.m_introLoaded -= OnGameIntroLoaded; + LoadingManager.instance.m_introLoaded -= CheckForIncompatibleMods; } public void OnSettingsUI(UIHelperBase helper) { Options.makeSettings(helper); } - private static void OnGameIntroLoaded() { + private static void CheckForIncompatibleMods() { if (GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { ModsCompatibilityChecker mcc = new ModsCompatibilityChecker(); mcc.PerformModCheck(); diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index f7400dc85..147d6eff1 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -7,27 +7,30 @@ using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; -using ICities; using UnityEngine; using static ColossalFramework.Plugins.PluginManager; -namespace TrafficManager.UI { - public class IncompatibleModsPanel : UIPanel { - private const ulong LOCAL_TMPE = 0u; +namespace TrafficManager.UI +{ + public class IncompatibleModsPanel : UIPanel + { + private const ulong LOCAL_MOD = ulong.MaxValue; + private static IncompatibleModsPanel _instance; private UILabel title; private UIButton closeButton; private UISprite warningIcon; private UIPanel mainPanel; private UICheckBox runModsCheckerOnStartup; private UIComponent blurEffect; - private static IncompatibleModsPanel _instance; - public Dictionary IncompatibleMods { get; set; } + public Dictionary IncompatibleMods { get; set; } - public void Initialize() { + public void Initialize() + { Log._Debug("IncompatibleModsPanel initialize"); - if (mainPanel != null) { + if (mainPanel != null) + { mainPanel.OnDestroy(); } @@ -42,7 +45,7 @@ public void Initialize() { mainPanel.height = 440; Vector2 resolution = UIView.GetAView().GetScreenResolution(); - relativePosition = new Vector3(resolution.x / 2 - 300, resolution.y / 3); + relativePosition = new Vector3((resolution.x / 2) - 300, resolution.y / 3); mainPanel.relativePosition = Vector3.zero; warningIcon = mainPanel.AddUIComponent(); @@ -80,15 +83,17 @@ public void Initialize() { runModsCheckerOnStartup.relativePosition = new Vector3(20, height - 30f); UIScrollablePanel scrollablePanel = panel.AddUIComponent(); - scrollablePanel.backgroundSprite = ""; + scrollablePanel.backgroundSprite = string.Empty; scrollablePanel.size = new Vector2(550, 340); scrollablePanel.relativePosition = new Vector3(0, 0); scrollablePanel.clipChildren = true; - if (IncompatibleMods.Count != 0) { + if (IncompatibleMods.Count != 0) + { int acc = 0; UIPanel item; - IncompatibleMods.ForEach((pair) => { + IncompatibleMods.ForEach((pair) => + { item = CreateEntry(ref scrollablePanel, pair.Value, pair.Key); item.relativePosition = new Vector2(0, acc); item.size = new Vector2(560, 50); @@ -129,7 +134,8 @@ public void Initialize() { blurEffect.size = new Vector2(resolution.x, resolution.y); blurEffect.absolutePosition = new Vector3(0, 0); blurEffect.SendToBack(); - if (blurEffect != null) { + if (blurEffect != null) + { blurEffect.isVisible = true; ValueAnimator.Animate("ModalEffect", delegate(float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); } @@ -137,12 +143,27 @@ public void Initialize() { BringToFront(); } - private void RunModsCheckerOnStartup_eventCheckChanged(bool value) { + protected override void OnKeyDown(UIKeyEventParameter p) + { + if (Input.GetKey(KeyCode.Escape) || Input.GetKey(KeyCode.Return)) + { + TryPopModal(); + p.Use(); + Hide(); + Unfocus(); + } + + base.OnKeyDown(p); + } + + private void RunModsCheckerOnStartup_eventCheckChanged(bool value) + { Log._Debug("Incompatible mods checker run on game launch changed to " + value); State.Options.setScanForKnownIncompatibleMods(value); } - private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) { + private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) + { closeButton.eventClick -= CloseButtonClick; TryPopModal(); Hide(); @@ -150,49 +171,72 @@ private void CloseButtonClick(UIComponent component, UIMouseEventParameter event eventparam.Use(); } - private UIPanel CreateEntry(ref UIScrollablePanel parent, string name, ulong steamId) { - string caption = steamId == LOCAL_TMPE ? "Delete" : "Unsubscribe"; + private UIPanel CreateEntry(ref UIScrollablePanel parent, string modName, PluginInfo mod) + { + string caption = mod.publishedFileID.AsUInt64 == LOCAL_MOD ? "Delete" : "Unsubscribe"; UIPanel panel = parent.AddUIComponent(); panel.size = new Vector2(560, 50); panel.backgroundSprite = "ContentManagerItemBackground"; UILabel label = panel.AddUIComponent(); - label.text = name; + label.text = modName; label.textAlignment = UIHorizontalAlignment.Left; label.relativePosition = new Vector2(10, 15); - CreateButton(panel, caption, (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); + CreateButton(panel, caption, (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, mod); }); return panel; } - private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, ulong steamId) { + private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, PluginInfo mod) + { bool success = false; + // disable button to prevent accidental clicks component.isEnabled = false; - if (steamId == LOCAL_TMPE) { - Log.Info("Trying to delete local build of TM:PE"); - success = DeleteLocalTMPE(); - } else { - Log.Info("Trying to unsubscribe workshop item " + steamId); - success = PlatformService.workshop.Unsubscribe(new PublishedFileId(steamId)); + Log.Info($"Removing incompatible mod '{mod.name}' from {mod.modPath}"); + if (mod.publishedFileID.AsUInt64 == LOCAL_MOD) + { + success = DeleteLocalTMPE(mod); + } + else + { + success = PlatformService.workshop.Unsubscribe(mod.publishedFileID); } - if (success) { - IncompatibleMods.Remove(steamId); + if (success) + { + IncompatibleMods.Remove(mod); component.parent.Disable(); component.isVisible = false; - Log.Info("Successfully removed incompatbile mod"); - } else { - Log.Warning("Failed to remove incompatible mod"); + } + else + { + Log.Warning($"Failed to remove mod '{mod.name}'"); component.isEnabled = true; } } - private UIButton CreateButton(UIComponent parent, string text, int x, int y, MouseEventHandler eventClick) { + private bool DeleteLocalTMPE(PluginInfo mod) + { + try + { + Log._Debug($"Deleting local TM:PE from {mod.modPath}"); + //mod.Unload(); + DirectoryUtils.DeleteDirectory(mod.modPath); + return true; + } + catch (Exception e) + { + return false; + } + } + + private UIButton CreateButton(UIComponent parent, string text, int x, int y, MouseEventHandler eventClick) + { var button = parent.AddUIComponent(); button.textScale = 0.8f; button.width = 150f; @@ -211,7 +255,8 @@ private UIButton CreateButton(UIComponent parent, string text, int x, int y, Mou return button; } - private void OnEnable() { + private void OnEnable() + { Log._Debug("IncompatibleModsPanel enabled"); PlatformService.workshop.eventUGCQueryCompleted += OnQueryCompleted; Singleton.instance.eventPluginsChanged += OnPluginsChanged; @@ -219,15 +264,18 @@ private void OnEnable() { LocaleManager.eventLocaleChanged += OnLocaleChanged; } - private void OnQueryCompleted(UGCDetails result, bool ioerror) { + private void OnQueryCompleted(UGCDetails result, bool ioerror) + { Log._Debug("IncompatibleModsPanel.OnQueryCompleted() - " + result.result.ToString("D") + " IO error?:" + ioerror); } - private void OnPluginsChanged() { + private void OnPluginsChanged() + { Log._Debug("IncompatibleModsPanel.OnPluginsChanged() - Plugins changed"); } - private void OnDisable() { + private void OnDisable() + { Log._Debug("IncompatibleModsPanel disabled"); PlatformService.workshop.eventUGCQueryCompleted -= this.OnQueryCompleted; Singleton.instance.eventPluginsChanged -= this.OnPluginsChanged; @@ -235,79 +283,22 @@ private void OnDisable() { LocaleManager.eventLocaleChanged -= this.OnLocaleChanged; } - protected override void OnKeyDown(UIKeyEventParameter p) { - if (Input.GetKey(KeyCode.Escape) || Input.GetKey(KeyCode.Return)) { - TryPopModal(); - p.Use(); - Hide(); - Unfocus(); - } - - base.OnKeyDown(p); - } - - private void TryPopModal() { - if (UIView.HasModalInput()) { - UIView.PopModal(); - UIComponent component = UIView.GetModalComponent(); - if (component != null) { - UIView.SetFocus(component); - } - } - - if (blurEffect != null && UIView.ModalInputCount() == 0) { - ValueAnimator.Animate("ModalEffect", delegate(float val) { blurEffect.opacity = val; }, new AnimatedFloat(1f, 0f, 0.7f, EasingType.CubicEaseOut), delegate() { blurEffect.Hide(); }); - } - } - - private bool DeleteLocalTMPE() + private void TryPopModal() { - PluginInfo localTMPE = GetLocalTMPE(); - try - { - Log._Debug("Attempt to Unload local TM:PE"); - localTMPE.Unload(); - Log._Debug("Attempt to delete local TM:PE folder " + localTMPE.modPath); - DirectoryUtils.DeleteDirectory(localTMPE.modPath); - return true; - } - catch (Exception e) - { - Log.Warning("Failed to delete local TM:PE from " + localTMPE.modPath); - return false; - } - } - - private PluginInfo GetLocalTMPE() - { - try + if (UIView.HasModalInput()) { - foreach (PluginInfo plugin in Singleton.instance.GetPluginsInfo()) + UIView.PopModal(); + UIComponent component = UIView.GetModalComponent(); + if (component != null) { - if (!plugin.isBuiltin && !plugin.isCameraScript && plugin.publishedFileID.AsUInt64 == ulong.MaxValue && GetModName(plugin).Contains("TM:PE")) - { - return plugin; - } + UIView.SetFocus(component); } } - catch (Exception e) - { - Log.Warning("IncompatibleModsWarning.GetLocalTMPE() error - see game log for details"); - Debug.LogException(e); - } - return null; - } - // returns name of mod as defined in the IUserMod class of that mod - private string GetModName(PluginInfo pluginInfo) - { - string name = pluginInfo.name; - IUserMod[] instances = pluginInfo.GetInstances(); - if ((int)instances.Length > 0) + if (blurEffect != null && UIView.ModalInputCount() == 0) { - name = instances[0].Name; + ValueAnimator.Animate("ModalEffect", delegate (float val) { blurEffect.opacity = val; }, new AnimatedFloat(1f, 0f, 0.7f, EasingType.CubicEaseOut), delegate () { blurEffect.Hide(); }); } - return name; } } } \ No newline at end of file diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index f419da89f..aa10afb21 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -1,138 +1,159 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; using ColossalFramework; -using ColossalFramework.PlatformServices; using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; using ICities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; using TrafficManager.UI; -using UnityEngine; using static ColossalFramework.Plugins.PluginManager; -namespace TrafficManager.Util { - public class ModsCompatibilityChecker { - //TODO include %APPDATA% mods folder - private const ulong LOCAL_TMPE = 0u; +namespace TrafficManager.Util +{ + public class ModsCompatibilityChecker + { + public const ulong LOCAL_MOD = ulong.MaxValue; + + // Used for LoadIncompatibleModsList() private const string RESOURCES_PREFIX = "TrafficManager.Resources."; - private const string DEFAULT_INCOMPATIBLE_MODS_FILENAME = "incompatible_mods.txt"; - private readonly ulong[] userModList; - private readonly Dictionary incompatibleModList; + private const string INCOMPATIBLE_MODS_FILE = "incompatible_mods.txt"; + + // parsed contents of incompatible_mods.txt + private readonly Dictionary incompatibleMods; - public ModsCompatibilityChecker() { - incompatibleModList = LoadIncompatibleModList(); - userModList = GetUserModsList(); + public ModsCompatibilityChecker() + { + incompatibleMods = LoadListOfIncompatibleMods(); } - public void PerformModCheck() { - Log.Info("Performing incompatible mods check"); - Dictionary incompatibleMods = new Dictionary(); - for (int i = 0; i < userModList.Length; i++) { - string incompatibleModName; - if (incompatibleModList.TryGetValue(userModList[i], out incompatibleModName)) { - incompatibleMods.Add(userModList[i], incompatibleModName); - } - } + public void PerformModCheck() + { + Dictionary detected = ScanForIncompatibleMods(); -#if !DEBUG - if (HasLocalTMPE()) + if (detected.Count > 0 && State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { - incompatibleMods.Add(LOCAL_TMPE, "TM:PE Local Build"); + IncompatibleModsPanel panel = UIView.GetAView().AddUIComponent(typeof(IncompatibleModsPanel)) as IncompatibleModsPanel; + panel.IncompatibleMods = detected; + panel.Initialize(); + UIView.PushModal(panel); + UIView.SetFocus(panel); } -#endif + } - if (incompatibleMods.Count > 0) { - Log.Warning("Incompatible mods detected! Count: " + incompatibleMods.Count); + /// + /// Iterates installed mods looking for known incompatibilities. + /// + /// + /// A list of detected incompatible mods. + /// + /// Invalid folder path (contains invalid characters, is empty, or contains only white spaces). + /// Path is too long (longer than the system-defined maximum length). + public Dictionary ScanForIncompatibleMods() + { + Log.Info("Scanning for incompatible mods"); - if (State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { - IncompatibleModsPanel panel = UIView.GetAView().AddUIComponent(typeof(IncompatibleModsPanel)) as IncompatibleModsPanel; - panel.IncompatibleMods = incompatibleMods; - panel.Initialize(); - UIView.PushModal(panel); - UIView.SetFocus(panel); - } - } else { - Log.Info("No incompatible mods detected"); - } - } + // list of installed incompatible mods + Dictionary results = new Dictionary(); - private Dictionary LoadIncompatibleModList() { - Dictionary incompatibleMods = new Dictionary(); - string[] lines; - using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream(RESOURCES_PREFIX + DEFAULT_INCOMPATIBLE_MODS_FILENAME)) { - using (StreamReader sr = new StreamReader(st)) { - lines = sr.ReadToEnd().Split(new string[] {"\n", "\r\n"}, StringSplitOptions.None); - } - } + // only check enabled mods? + bool filterToEnabled = State.GlobalConfig.Instance.Main.IgnoreDisabledMods; - for (int i = 0; i < lines.Length; i++) { - string[] strings = lines[i].Split(';'); - ulong steamId; - if (ulong.TryParse(strings[0], out steamId)) { - incompatibleMods.Add(steamId, strings[1]); + // iterate plugins + foreach (PluginInfo mod in Singleton.instance.GetPluginsInfo()) + { + if (!mod.isBuiltin && !mod.isCameraScript && (!filterToEnabled || mod.isEnabled)) + { + string modName = GetModName(mod); + + if (incompatibleMods.ContainsKey(mod.publishedFileID.AsUInt64)) + { + Log.Info($"Incompatible mod: {mod.publishedFileID.AsUInt64} - {modName}"); + results.Add(mod, modName); + } +#if !DEBUG + // Workshop TM:PE builds treat local builds as incompatible + else if (mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) + { + Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); + string folder = Path.GetDirectoryName(mod.modPath).Split(Path.DirectorySeparatorChar).Last(); + results.Add(mod, $"{modName} in /{folder}"); + } +#endif } } - // Treat any other branches of TM:PE as incompatible (causes issues with saving road customisations). - // This still won't detect local builds, but will reduce support workload from end users who have - // multiple TM:PE subscribed. - // See also: https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues/211 -#if LABS - incompatibleMods.Add(583429740u , "TM:PE STABLE"); -#elif DEBUG // assume local build - incompatibleMods.Add(1637663252u, "TM:PE LABS"); - incompatibleMods.Add(583429740u , "TM:PE STABLE"); -#else // STABLE - incompatibleMods.Add(1637663252u, "TM:PE LABS"); -#endif + Log.Info($"Scan complete: {results.Count} incompatible mod(s) found"); - return incompatibleMods; + return results; } - private ulong[] GetUserModsList() { - if (State.GlobalConfig.Instance.Main.IgnoreDisabledMods) { - return PluginManager.instance.GetPluginsInfo().Where(plugin => plugin.isEnabled).Select(info => info.publishedFileID.AsUInt64).ToArray(); + /// + /// Gets the name of the specified mod. + /// + /// It will return the if found, otherwise it will return (assembly name). + /// + /// + /// The associated with the mod. + /// + /// The name of the specified plugin. + public string GetModName(PluginInfo plugin) + { + string name = plugin.name; + IUserMod[] instances = plugin.GetInstances(); + if (instances.Length > 0) + { + name = instances[0].Name; } - PublishedFileId[] ids = ContentManagerPanel.subscribedItemsTable.ToArray(); - return ids.Select(id => id.AsUInt64).ToArray(); + return name; } - private bool HasLocalTMPE() + /// + /// Loads and parses the incompatible_mods.txt resource, adds other workshop branches of TM:PE as applicable. + /// + /// + /// A dictionary of mod names referenced by Steam Workshop ID. + private Dictionary LoadListOfIncompatibleMods() { - try + // list of known incompatible mods + Dictionary results = new Dictionary(); + + // load the file + string[] lines; + using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream(RESOURCES_PREFIX + INCOMPATIBLE_MODS_FILE)) { - foreach (PluginInfo plugin in Singleton.instance.GetPluginsInfo()) + using (StreamReader sr = new StreamReader(st)) { - // How to detect local mod: plugin.publishedFileID.AsUInt64 == ulong.MaxValue - if (!plugin.isBuiltin && !plugin.isCameraScript && plugin.publishedFileID.AsUInt64 == ulong.MaxValue && GetModName(plugin).Contains("TM:PE")) - { - return true; - } + lines = sr.ReadToEnd().Split(new string[] { "\n", "\r\n" }, StringSplitOptions.None); } } - catch (Exception e) - { - Log.Warning("ModsCompatibilityChecker.HasLocalTMPE() error - see game log for details"); - Debug.LogException(e); - } - return false; - } - // returns name of mod as defined in the IUserMod class of that mod - private string GetModName(PluginInfo plugin) - { - string name = plugin.name; - IUserMod[] instances = plugin.GetInstances(); - if ((int)instances.Length > 0) + Log.Info($"{INCOMPATIBLE_MODS_FILE} contains {lines.Length} entries"); + + // parse the file + for (int i = 0; i < lines.Length; i++) { - name = instances[0].Name; + string[] strings = lines[i].Split(';'); + if (ulong.TryParse(strings[0], out ulong steamId)) + { + results.Add(steamId, strings[1]); + } } - return name; + + // Treat other workshop-published branches of TM:PE, as applicable, as conflicts +#if LABS + results.Add(583429740u , "TM:PE STABLE"); +#elif DEBUG + results.Add(1637663252u, "TM:PE LABS"); + results.Add(583429740u, "TM:PE STABLE"); +#else + results.Add(1637663252u, "TM:PE LABS"); +#endif + + return results; } } } \ No newline at end of file From 3cc109858cbe1962360f121493f73aa14cf5c6aa Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 6 Jun 2019 01:42:35 +0100 Subject: [PATCH 016/142] Fixed inline variable declaration that was breaking build https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/pull/333#issuecomment-499038086 --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index aa10afb21..244c8f144 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -137,7 +137,8 @@ private Dictionary LoadListOfIncompatibleMods() for (int i = 0; i < lines.Length; i++) { string[] strings = lines[i].Split(';'); - if (ulong.TryParse(strings[0], out ulong steamId)) + ulong steamId; + if (ulong.TryParse(strings[0], out steamId)) { results.Add(steamId, strings[1]); } From 290e9ca974a7b52e74ab87c1cfd2b2c0f18b88ab Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 6 Jun 2019 05:15:32 +0100 Subject: [PATCH 017/142] Update ModsCompatibilityChecker.cs Just trying to restart the build process. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 244c8f144..f27c692f5 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -136,8 +136,8 @@ private Dictionary LoadListOfIncompatibleMods() // parse the file for (int i = 0; i < lines.Length; i++) { - string[] strings = lines[i].Split(';'); ulong steamId; + string[] strings = lines[i].Split(';'); if (ulong.TryParse(strings[0], out steamId)) { results.Add(steamId, strings[1]); From b914f9045167813739f43d4cdbcedfcbfa1d3933 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 6 Jun 2019 05:27:42 +0100 Subject: [PATCH 018/142] Fix path display for local builds `Path.GetDirectoryName(mod.modPath)` was assuming last item on the path was a file, but `mod.modPath` doesn't include a file. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index f27c692f5..115215b7d 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -79,7 +79,7 @@ public Dictionary ScanForIncompatibleMods() else if (mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) { Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); - string folder = Path.GetDirectoryName(mod.modPath).Split(Path.DirectorySeparatorChar).Last(); + string folder = mod.modPath.Split(Path.DirectorySeparatorChar).Last(); results.Add(mod, $"{modName} in /{folder}"); } #endif From f600d0065e5ae4f363e263fe2f9eed88ec70d553 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 6 Jun 2019 05:32:30 +0100 Subject: [PATCH 019/142] Removed unnecessary events --- TLM/TLM/UI/IncompatibleModsPanel.cs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index 147d6eff1..d58263043 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -255,34 +255,6 @@ private UIButton CreateButton(UIComponent parent, string text, int x, int y, Mou return button; } - private void OnEnable() - { - Log._Debug("IncompatibleModsPanel enabled"); - PlatformService.workshop.eventUGCQueryCompleted += OnQueryCompleted; - Singleton.instance.eventPluginsChanged += OnPluginsChanged; - Singleton.instance.eventPluginsStateChanged += OnPluginsChanged; - LocaleManager.eventLocaleChanged += OnLocaleChanged; - } - - private void OnQueryCompleted(UGCDetails result, bool ioerror) - { - Log._Debug("IncompatibleModsPanel.OnQueryCompleted() - " + result.result.ToString("D") + " IO error?:" + ioerror); - } - - private void OnPluginsChanged() - { - Log._Debug("IncompatibleModsPanel.OnPluginsChanged() - Plugins changed"); - } - - private void OnDisable() - { - Log._Debug("IncompatibleModsPanel disabled"); - PlatformService.workshop.eventUGCQueryCompleted -= this.OnQueryCompleted; - Singleton.instance.eventPluginsChanged -= this.OnPluginsChanged; - Singleton.instance.eventPluginsStateChanged -= this.OnPluginsChanged; - LocaleManager.eventLocaleChanged -= this.OnLocaleChanged; - } - private void TryPopModal() { if (UIView.HasModalInput()) From 366bfde178273aa54595c9854c91d5d7e989fd69 Mon Sep 17 00:00:00 2001 From: Victor-Philipp Negoescu Date: Fri, 7 Jun 2019 00:46:26 +0200 Subject: [PATCH 020/142] #225: emergency vehicles do not change lanes at toll booths --- TLM/TLM/Custom/AI/CustomVehicleAI.cs | 80 +++++------ TLM/TLM/Manager/IVehicleBehaviorManager.cs | 15 +++ .../Manager/Impl/VehicleBehaviorManager.cs | 126 ++++++++++++++++++ 3 files changed, 175 insertions(+), 46 deletions(-) diff --git a/TLM/TLM/Custom/AI/CustomVehicleAI.cs b/TLM/TLM/Custom/AI/CustomVehicleAI.cs index ccb09ee71..72fc5c692 100644 --- a/TLM/TLM/Custom/AI/CustomVehicleAI.cs +++ b/TLM/TLM/Custom/AI/CustomVehicleAI.cs @@ -202,66 +202,54 @@ protected void CustomUpdatePathTargetPositions(ushort vehicleID, ref Vehicle veh // find next lane (emergency vehicles / dynamic lane selection) int bestLaneIndex = nextPathPos.m_lane; if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != (Vehicle.Flags)0) { +#if ROUTING + bestLaneIndex = VehicleBehaviorManager.Instance.FindBestEmergencyLane(vehicleID, ref vehicleData, ref VehicleStateManager.Instance.VehicleStates[vehicleID], curLaneId, currentPosition, curSegmentInfo, nextPathPos, nextSegmentInfo); +#else + // stock procedure for emergency vehicles on duty bestLaneIndex = FindBestLane(vehicleID, ref vehicleData, nextPathPos); - } else { +#endif + } else if (VehicleBehaviorManager.Instance.MayFindBestLane(vehicleID, ref vehicleData, ref VehicleStateManager.Instance.VehicleStates[vehicleID])) { // NON-STOCK CODE START if (firstIter && this.m_info.m_vehicleType == VehicleInfo.VehicleType.Car && !this.m_info.m_isLargeVehicle ) { - bool mayFindBestLane = false; -#if BENCHMARK - using (var bm = new Benchmark(null, "MayFindBestLane")) { -#endif - mayFindBestLane = VehicleBehaviorManager.Instance.MayFindBestLane(vehicleID, ref vehicleData, ref VehicleStateManager.Instance.VehicleStates[vehicleID]); -#if BENCHMARK - } -#endif - - if (mayFindBestLane) { - uint next2PathId = nextPathId; - int next2PathPosIndex = nextCoarsePathPosIndex; - bool next2Invalid; - PathUnit.Position next2PathPos; - NetInfo next2SegmentInfo = null; - PathUnit.Position next3PathPos; - NetInfo next3SegmentInfo = null; - PathUnit.Position next4PathPos; - if (PathUnit.GetNextPosition(ref next2PathId, ref next2PathPosIndex, out next2PathPos, out next2Invalid)) { - next2SegmentInfo = netManager.m_segments.m_buffer[(int)next2PathPos.m_segment].Info; - - uint next3PathId = next2PathId; - int next3PathPosIndex = next2PathPosIndex; - bool next3Invalid; - if (PathUnit.GetNextPosition(ref next3PathId, ref next3PathPosIndex, out next3PathPos, out next3Invalid)) { - next3SegmentInfo = netManager.m_segments.m_buffer[(int)next3PathPos.m_segment].Info; - - uint next4PathId = next3PathId; - int next4PathPosIndex = next3PathPosIndex; - bool next4Invalid; - if (!PathUnit.GetNextPosition(ref next4PathId, ref next4PathPosIndex, out next4PathPos, out next4Invalid)) { - next4PathPos = default(PathUnit.Position); - } - } else { - next3PathPos = default(PathUnit.Position); + uint next2PathId = nextPathId; + int next2PathPosIndex = nextCoarsePathPosIndex; + bool next2Invalid; + PathUnit.Position next2PathPos; + NetInfo next2SegmentInfo = null; + PathUnit.Position next3PathPos; + NetInfo next3SegmentInfo = null; + PathUnit.Position next4PathPos; + if (PathUnit.GetNextPosition(ref next2PathId, ref next2PathPosIndex, out next2PathPos, out next2Invalid)) { + next2SegmentInfo = netManager.m_segments.m_buffer[(int)next2PathPos.m_segment].Info; + + uint next3PathId = next2PathId; + int next3PathPosIndex = next2PathPosIndex; + bool next3Invalid; + if (PathUnit.GetNextPosition(ref next3PathId, ref next3PathPosIndex, out next3PathPos, out next3Invalid)) { + next3SegmentInfo = netManager.m_segments.m_buffer[(int)next3PathPos.m_segment].Info; + + uint next4PathId = next3PathId; + int next4PathPosIndex = next3PathPosIndex; + bool next4Invalid; + if (!PathUnit.GetNextPosition(ref next4PathId, ref next4PathPosIndex, out next4PathPos, out next4Invalid)) { next4PathPos = default(PathUnit.Position); } } else { - next2PathPos = default(PathUnit.Position); next3PathPos = default(PathUnit.Position); next4PathPos = default(PathUnit.Position); } - -#if BENCHMARK - using (var bm = new Benchmark(null, "FindBestLane")) { -#endif - bestLaneIndex = VehicleBehaviorManager.Instance.FindBestLane(vehicleID, ref vehicleData, ref VehicleStateManager.Instance.VehicleStates[vehicleID], curLaneId, currentPosition, curSegmentInfo, nextPathPos, nextSegmentInfo, next2PathPos, next2SegmentInfo, next3PathPos, next3SegmentInfo, next4PathPos); -#if BENCHMARK - } -#endif + } else { + next2PathPos = default(PathUnit.Position); + next3PathPos = default(PathUnit.Position); + next4PathPos = default(PathUnit.Position); } - // NON-STOCK CODE END + + bestLaneIndex = VehicleBehaviorManager.Instance.FindBestLane(vehicleID, ref vehicleData, ref VehicleStateManager.Instance.VehicleStates[vehicleID], curLaneId, currentPosition, curSegmentInfo, nextPathPos, nextSegmentInfo, next2PathPos, next2SegmentInfo, next3PathPos, next3SegmentInfo, next4PathPos); } + // NON-STOCK CODE END } // update lane index diff --git a/TLM/TLM/Manager/IVehicleBehaviorManager.cs b/TLM/TLM/Manager/IVehicleBehaviorManager.cs index 2a8ca852e..78fcc5da7 100644 --- a/TLM/TLM/Manager/IVehicleBehaviorManager.cs +++ b/TLM/TLM/Manager/IVehicleBehaviorManager.cs @@ -46,6 +46,21 @@ public interface IVehicleBehaviorManager { /// target position lane index int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position next1PathPos, NetInfo next1SegInfo, PathUnit.Position next2PathPos, NetInfo next2SegInfo, PathUnit.Position next3PathPos, NetInfo next3SegInfo, PathUnit.Position next4PathPos); + /// + /// Identifies the best lane to take on the next segment (for emergency vehicles on duty). + /// Note that this method does not require Advanced AI and/or DLS to be active. + /// + /// queried vehicle id + /// vehicle data + /// vehicle state + /// current lane id + /// current path position + /// current segment info + /// next path position + /// next segment info + /// target position lane index + int FindBestEmergencyLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position nextPathPos, NetInfo nextSegInfo); + /// /// Determines if the given vehicle is allowed to find an alternative lane. /// diff --git a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs index 7d13a3803..552d6b650 100644 --- a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs @@ -1453,6 +1453,132 @@ public int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleSt return next1PathPos.m_lane; } + public int FindBestEmergencyLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position nextPathPos, NetInfo nextSegInfo) { + try { + GlobalConfig conf = GlobalConfig.Instance; +#if DEBUG + bool debug = false; + if (conf.Debug.Switches[17]) { + ushort nodeId = Services.NetService.GetSegmentNodeId(currentPathPos.m_segment, currentPathPos.m_offset < 128); + debug = (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId) && (conf.Debug.NodeId == 0 || conf.Debug.NodeId == nodeId); + } + + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): currentLaneId={currentLaneId}, currentPathPos=[seg={currentPathPos.m_segment}, lane={currentPathPos.m_lane}, off={currentPathPos.m_offset}] nextPathPos=[seg={nextPathPos.m_segment}, lane={nextPathPos.m_lane}, off={nextPathPos.m_offset}]"); + } +#endif + + // cur -> next + float curPosition = 0f; + if (currentPathPos.m_lane < currentSegInfo.m_lanes.Length) { + curPosition = currentSegInfo.m_lanes[currentPathPos.m_lane].m_position; + } + float vehicleLength = 1f + vehicleState.totalLength; + bool startNode = currentPathPos.m_offset < 128; + uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); + +#if DEBUG + if (currentFwdRoutingIndex >= RoutingManager.Instance.laneEndForwardRoutings.Length) { + Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Invalid array index: currentFwdRoutingIndex={currentFwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.laneEndForwardRoutings.Length} (currentLaneId={currentLaneId}, startNode={startNode})"); + } +#endif + + if (!RoutingManager.Instance.laneEndForwardRoutings[currentFwdRoutingIndex].routed) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward routing for next path position available."); + } +#endif + return nextPathPos.m_lane; + } + + LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.laneEndForwardRoutings[currentFwdRoutingIndex].transitions; + + if (currentFwdTransitions == null) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); + } +#endif + return nextPathPos.m_lane; + } + +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Starting lane-finding algorithm now. vehicleLength={vehicleLength}"); + } +#endif + + float minCost = float.MaxValue; + byte bestNextLaneIndex = nextPathPos.m_lane; + for (int i = 0; i < currentFwdTransitions.Length; ++i) { + if (currentFwdTransitions[i].segmentId != nextPathPos.m_segment) { + continue; + } + + if (!( + currentFwdTransitions[i].type == LaneEndTransitionType.Default || + currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || + currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed + )) { + continue; + } + + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, nextPathPos.m_segment, currentFwdTransitions[i].laneIndex, nextSegInfo)) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); + } +#endif + continue; + } + + NetInfo.Lane nextLaneInfo = nextSegInfo.m_lanes[currentFwdTransitions[i].laneIndex]; + + /* + * Check reserved space on next lane + */ +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Checking for traffic on next lane id={currentFwdTransitions[i].laneId}."); + } +#endif + + Services.NetService.ProcessLane(currentFwdTransitions[i].laneId, (uint nextLaneId, ref NetLane nextLane) => { + // similar to stock code in VehicleAI.FindBestLane + float cost = nextLane.GetReservedSpace(); + if (currentFwdTransitions[i].laneIndex == nextPathPos.m_lane) { + cost -= vehicleLength; + } + + cost += Mathf.Abs(curPosition - nextLaneInfo.m_position) * 0.1f; + + if (cost < minCost) { + minCost = cost; + bestNextLaneIndex = currentFwdTransitions[i].laneIndex; + +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Found better lane: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); + } +#endif + } + return true; + }); + } // for each forward transition + +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Best lane identified: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); + } +#endif + return bestNextLaneIndex; + } catch (Exception e) { + Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Exception occurred: {e}"); + } + return nextPathPos.m_lane; + } + public bool MayFindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState) { GlobalConfig conf = GlobalConfig.Instance; #if DEBUG From da99c7f147e9723ce169a9e6df9f52ba512730e0 Mon Sep 17 00:00:00 2001 From: Victor-Philipp Negoescu Date: Fri, 7 Jun 2019 22:13:42 +0200 Subject: [PATCH 021/142] #259: - Parking AI: Last segment before parking space is now required to be accessible by car - Bugfix: Random parking broken - Bugfix: Pedestrian crossing restriction affects road-side parking - Pedestrian crossing is allowed at untouchable bent nodes - Removed u-turn lane arrow check at transition nodes --- TLM/TLM/Custom/AI/CustomCitizenAI.cs | 2 +- TLM/TLM/Custom/AI/CustomPassengerCarAI.cs | 66 ++++++++++++++----- TLM/TLM/Custom/PathFinding/CustomPathFind2.cs | 30 ++++++--- .../Custom/PathFinding/CustomPathManager.cs | 26 +++++--- .../Manager/Impl/AdvancedParkingManager.cs | 2 +- .../Impl/JunctionRestrictionsManager.cs | 3 +- TLM/TLM/Manager/Impl/RoutingManager.cs | 2 +- TLM/TLM/Traffic/Data/ExtCitizenInstance.cs | 4 +- 8 files changed, 95 insertions(+), 40 deletions(-) diff --git a/TLM/TLM/Custom/AI/CustomCitizenAI.cs b/TLM/TLM/Custom/AI/CustomCitizenAI.cs index 1eb4aef12..b9649a7b1 100644 --- a/TLM/TLM/Custom/AI/CustomCitizenAI.cs +++ b/TLM/TLM/Custom/AI/CustomCitizenAI.cs @@ -563,7 +563,7 @@ public bool ExtStartPathFind(ushort instanceID, ref CitizenInstance instanceData } public bool CustomFindPathPosition(ushort instanceID, ref CitizenInstance citizenData, Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowUnderground, out PathUnit.Position position) { - return CustomPathManager.FindCitizenPathPosition(pos, laneTypes, vehicleTypes, (citizenData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None, allowUnderground, out position); + return CustomPathManager.FindCitizenPathPosition(pos, laneTypes, vehicleTypes, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, (citizenData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None, allowUnderground, out position); } // stock code diff --git a/TLM/TLM/Custom/AI/CustomPassengerCarAI.cs b/TLM/TLM/Custom/AI/CustomPassengerCarAI.cs index 94cfbbc66..f7cb1c7d1 100644 --- a/TLM/TLM/Custom/AI/CustomPassengerCarAI.cs +++ b/TLM/TLM/Custom/AI/CustomPassengerCarAI.cs @@ -135,7 +135,7 @@ public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort d bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (debug) - Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): called for vehicle {vehicleID}, driverInstanceId={driverInstanceId}, startPos={startPos}, endPos={endPos}, sourceBuilding={vehicleData.m_sourceBuilding}, targetBuilding={vehicleData.m_targetBuilding} pathMode={driverExtInstance.pathMode}"); + Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): called for vehicle {vehicleID}, driverInstanceId={driverInstanceId}, startPos={startPos}, endPos={endPos}, sourceBuilding={driverInstance.m_sourceBuilding}, targetBuilding={driverInstance.m_targetBuilding} pathMode={driverExtInstance.pathMode}"); #endif PathUnit.Position startPosA = default(PathUnit.Position); @@ -149,7 +149,10 @@ public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort d // NON-STOCK CODE START bool calculateEndPos = true; - bool allowRandomParking = true; + bool randomParking = targetBuildingId != 0 && ( + Singleton.instance.m_buildings.m_buffer[(int)targetBuildingId].Info.m_class.m_service > ItemClass.Service.Office || + (driverInstance.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None + ); bool movingToParkingPos = false; bool foundStartingPos = false; bool skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; @@ -167,9 +170,10 @@ public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort d if (driverExtInstance.pathMode == ExtPathMode.RequiresMixedCarPathToTarget) { driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; startBothWays = false; + randomParking = true; #if DEBUG if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was RequiresDirectCarPathToTarget: Parking spaces will NOT be searched beforehand. Setting pathMode={driverExtInstance.pathMode}"); + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was RequiresMixedCarPathToTarget: Parking spaces will NOT be searched beforehand. Setting pathMode={driverExtInstance.pathMode}"); #endif } else if ( driverExtInstance.pathMode != ExtPathMode.ParkingFailed && @@ -215,6 +219,12 @@ public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort d } startBothWays = false; +#if DEBUG + if (driverExtInstance.failedParkingAttempts > 15) { + Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Detected long Parking AI loop! failedParkingAttempts={driverExtInstance.failedParkingAttempts}"); + } +#endif + if (driverExtInstance.failedParkingAttempts > GlobalConfig.Instance.ParkingAI.MaxParkingAttempts) { // maximum number of parking attempts reached #if DEBUG @@ -247,7 +257,7 @@ public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort d Vector3 returnPos = searchAtCurrentPos ? (Vector3)vehicleData.m_targetPos3 : endPos; if (AdvancedParkingManager.Instance.FindParkingSpaceForCitizen(returnPos, vehicleData.Info, ref driverExtInstance, homeId, targetBuildingId == homeId, vehicleID, allowTourists, out parkPos, ref endPosA, out calcEndPos)) { calculateEndPos = calcEndPos; - allowRandomParking = false; + randomParking = false; movingToParkingPos = true; if (!driverExtInstance.CalculateReturnPath(parkPos, returnPos)) { @@ -303,17 +313,7 @@ public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort d VehicleInfo.VehicleType vehicleTypes = this.m_info.m_vehicleType; bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0; - bool randomParking = false; bool combustionEngine = this.m_info.m_class.m_subService == ItemClass.SubService.ResidentialLow; - if (allowRandomParking && // NON-STOCK CODE - !movingToParkingPos && - targetBuildingId != 0 && - ( - Singleton.instance.m_buildings.m_buffer[(int)targetBuildingId].Info.m_class.m_service > ItemClass.Service.Office || - (driverInstance.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None - )) { - randomParking = true; - } #if DEBUG if (fineDebug) @@ -325,7 +325,43 @@ public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort d foundStartingPos = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, vehicleTypes, allowUnderground, false, 32f, out startPosA, out startPosB, out sqrDistA, out sqrDistB); } - bool foundEndPos = !calculateEndPos || driverInstance.Info.m_citizenAI.FindPathPosition(driverInstanceId, ref driverInstance, endPos, Options.prohibitPocketCars && (targetBuildingId == 0 || (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None) ? NetInfo.LaneType.Pedestrian : (laneTypes | NetInfo.LaneType.Pedestrian), vehicleTypes, undergroundTarget, out endPosA); + NetInfo.LaneType endPosLaneTypes = NetInfo.LaneType.None; + NetInfo.LaneType otherLaneTypes = NetInfo.LaneType.None; + VehicleInfo.VehicleType otherVehicleTypes = VehicleInfo.VehicleType.None; + + if (movingToParkingPos) { + endPosLaneTypes = NetInfo.LaneType.Pedestrian; + otherLaneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + otherVehicleTypes = VehicleInfo.VehicleType.Car; +#if DEBUG + if (fineDebug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Vehicle is moving to a parking spot."); +#endif + } else { + endPosLaneTypes = + Options.prohibitPocketCars && ( + targetBuildingId == 0 || + (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None + ) + ? NetInfo.LaneType.Pedestrian + : (laneTypes | NetInfo.LaneType.Pedestrian); +#if DEBUG + if (fineDebug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Vehicle is NOT moving to a parking spot. endPosLaneTypes={endPosLaneTypes}"); +#endif + } + + bool foundEndPos = + !calculateEndPos || + CustomPathManager.FindCitizenPathPosition( + endPos, + endPosLaneTypes, + vehicleTypes, + otherLaneTypes, + otherVehicleTypes, + (driverInstance.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None, + undergroundTarget, + out endPosA); // NON-STOCK CODE END if (foundStartingPos && diff --git a/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs b/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs index fbcff6bef..465ccd425 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs @@ -267,7 +267,7 @@ private void PathFindImplementation(uint unit, ref PathUnit data) { m_laneTypes = (NetInfo.LaneType)m_pathUnits.m_buffer[unit].m_laneTypes; m_vehicleTypes = (VehicleInfo.VehicleType)m_pathUnits.m_buffer[unit].m_vehicleTypes; m_maxLength = m_pathUnits.m_buffer[unit].m_length; - m_pathFindIndex = (m_pathFindIndex + 1 & 0x7FFF); + m_pathFindIndex = m_pathFindIndex + 1 & 0x7FFF; m_pathRandomizer = new Randomizer(unit); m_carBanMask = NetSegment.Flags.CarBan; @@ -280,12 +280,12 @@ private void PathFindImplementation(uint unit, ref PathUnit data) { m_carBanMask |= NetSegment.Flags.WaitingPath; } - m_ignoreBlocked = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_BLOCKED) != 0); - m_stablePath = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_STABLE_PATH) != 0); - m_randomParking = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_RANDOM_PARKING) != 0); - m_transportVehicle = ((m_laneTypes & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None); - m_ignoreCost = (m_stablePath || (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_COST) != 0); - m_disableMask = (NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed); + m_ignoreBlocked = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_BLOCKED) != 0; + m_stablePath = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_STABLE_PATH) != 0; + m_randomParking = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_RANDOM_PARKING) != 0; + m_transportVehicle = (m_laneTypes & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None; + m_ignoreCost = m_stablePath || (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_COST) != 0; + m_disableMask = NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed; if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_FLOODED) == 0) { m_disableMask |= NetSegment.Flags.Flooded; @@ -2402,6 +2402,7 @@ private void ProcessItemPedBicycle( } // NON-STOCK CODE START + bool mayCross = true; #if JUNCTIONRESTRICTIONS || CUSTOMTRAFFICLIGHTS if (Options.junctionRestrictionsEnabled || Options.timedLightsEnabled) { bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; @@ -2412,10 +2413,10 @@ private void ProcessItemPedBicycle( if (!m_junctionManager.IsPedestrianCrossingAllowed(nextSegmentId, nextIsStartNode)) { #if DEBUG if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Pedestrian crossing prohibited"); + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Pedestrian crossing prohibited"); } #endif - return; + mayCross = false; } } #endif @@ -2491,6 +2492,17 @@ private void ProcessItemPedBicycle( nextItem.m_position.m_lane = (byte)nextLaneIndex; nextItem.m_position.m_offset = offset; + // NON-STOCK CODE START + if (!mayCross && nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Crossing prohibited"); + } +#endif + return; + } + // NON-STOCK CODE END + if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { diff --git a/TLM/TLM/Custom/PathFinding/CustomPathManager.cs b/TLM/TLM/Custom/PathFinding/CustomPathManager.cs index 0a8b1cb2c..6402e09d7 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathManager.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathManager.cs @@ -331,22 +331,25 @@ public bool CreatePath(out uint unit, ref Randomizer randomizer, PathCreationArg unit = pathUnitId; if (args.isHeavyVehicle) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 16; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IS_HEAVY; } if (args.ignoreBlocked || args.ignoreFlooded) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 32; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IGNORE_BLOCKED; } if (args.stablePath) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 64; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_STABLE_PATH; } if (args.randomParking) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 2; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_RANDOM_PARKING; + } + if (args.ignoreFlooded) { + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IGNORE_FLOODED; } if (args.hasCombustionEngine) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 4; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_COMBUSTION; } if (args.ignoreCosts) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 8; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IGNORE_COST; } this.m_pathUnits.m_buffer[unit].m_pathFindFlags = 0; this.m_pathUnits.m_buffer[unit].m_buildIndex = args.buildIndex; @@ -568,15 +571,18 @@ public static bool FindPathPositionWithSpiralLoop(Vector3 position, Vector3? sec /// /// Finds a suitable path position for a walking citizen with the given world position. + /// If secondary lane constraints are given also checks whether there exists another lane that matches those constraints. /// /// world position /// allowed lane types /// allowed vehicle types + /// allowed lane types for secondary lane + /// other vehicle types for secondary lane /// public transport allowed? /// underground position allowed? /// resulting path position /// true if a position could be found, false otherwise - public static bool FindCitizenPathPosition(Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowTransport, bool allowUnderground, out PathUnit.Position position) { + public static bool FindCitizenPathPosition(Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, NetInfo.LaneType otherLaneTypes, VehicleInfo.VehicleType otherVehicleTypes, bool allowTransport, bool allowUnderground, out PathUnit.Position position) { // TODO move to ExtPathManager after harmony upgrade position = default(PathUnit.Position); float minDist = 1E+10f; @@ -584,15 +590,15 @@ public static bool FindCitizenPathPosition(Vector3 pos, NetInfo.LaneType laneTyp PathUnit.Position posB; float distA; float distB; - if (PathManager.FindPathPosition(pos, ItemClass.Service.Road, laneTypes, vehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + if (FindPathPositionWithSpiralLoop(pos, ItemClass.Service.Road, laneTypes, vehicleTypes, otherLaneTypes, otherVehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { minDist = distA; position = posA; } - if (PathManager.FindPathPosition(pos, ItemClass.Service.Beautification, laneTypes, vehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + if (FindPathPositionWithSpiralLoop(pos, ItemClass.Service.Beautification, laneTypes, vehicleTypes, otherLaneTypes, otherVehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { minDist = distA; position = posA; } - if (allowTransport && PathManager.FindPathPosition(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + if (allowTransport && FindPathPositionWithSpiralLoop(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, otherLaneTypes, otherVehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { minDist = distA; position = posA; } diff --git a/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs b/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs index 10245008c..d088f5181 100644 --- a/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs +++ b/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs @@ -1187,7 +1187,7 @@ public bool FindParkingSpaceForCitizen(Vector3 endPos, VehicleInfo vehicleInfo, } } else if (knownParkingSpaceLocation == ExtParkingSpaceLocation.Building) { // found a building with parking space - if (CustomPathManager.FindPathPositionWithSpiralLoop(parkPos, endPos, ItemClass.Service.Road, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out endPathPos)) { + if (CustomPathManager.FindPathPositionWithSpiralLoop(parkPos, endPos, ItemClass.Service.Road, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out endPathPos)) { calculateEndPos = false; } diff --git a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs index af6af352b..d2959df5b 100644 --- a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs @@ -466,7 +466,8 @@ public bool GetDefaultPedestrianCrossingAllowed(ushort segmentId, bool startNode return true; } - bool ret = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; + // crossing is allowed at junctions and at untouchable nodes (for example: spiral underground parking) + bool ret = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Untouchable)) != NetNode.Flags.None; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultPedestrianCrossingAllowed({segmentId}, {startNode}): Setting is configurable. ret={ret}, flags={node.m_flags}"); diff --git a/TLM/TLM/Manager/Impl/RoutingManager.cs b/TLM/TLM/Manager/Impl/RoutingManager.cs index a4fc04c40..f5d0ffc78 100644 --- a/TLM/TLM/Manager/Impl/RoutingManager.cs +++ b/TLM/TLM/Manager/Impl/RoutingManager.cs @@ -595,7 +595,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui (nextIncomingDir == ArrowDirection.Right && hasLeftArrow) || // valid incoming right (nextIncomingDir == ArrowDirection.Left && hasRightArrow) || // valid incoming left (nextIncomingDir == ArrowDirection.Forward && hasForwardArrow) || // valid incoming straight - (nextIncomingDir == ArrowDirection.Turn && (nextIsEndOrOneWayOut || ((leftHandDrive && hasRightArrow) || (!leftHandDrive && hasLeftArrow))))) { // valid turning lane + (nextIncomingDir == ArrowDirection.Turn && (nextIsSimpleJunction || nextIsEndOrOneWayOut || ((leftHandDrive && hasRightArrow) || (!leftHandDrive && hasLeftArrow))))) { // valid turning lane #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane arrow check passed for nextLaneId={nextLaneId}, idx={nextLaneIndex}. adding as default lane."); diff --git a/TLM/TLM/Traffic/Data/ExtCitizenInstance.cs b/TLM/TLM/Traffic/Data/ExtCitizenInstance.cs index 4555835c0..412843d49 100644 --- a/TLM/TLM/Traffic/Data/ExtCitizenInstance.cs +++ b/TLM/TLM/Traffic/Data/ExtCitizenInstance.cs @@ -359,8 +359,8 @@ internal bool CalculateReturnPath(Vector3 parkPos, Vector3 targetPos) { PathUnit.Position parkPathPos; PathUnit.Position targetPathPos = default(PathUnit.Position); - bool foundParkPathPos = CustomPathManager.FindCitizenPathPosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, false, false, out parkPathPos); - bool foundTargetPathPos = foundParkPathPos && CustomPathManager.FindCitizenPathPosition(targetPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, false, false, out targetPathPos); + bool foundParkPathPos = CustomPathManager.FindCitizenPathPosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out parkPathPos); + bool foundTargetPathPos = foundParkPathPos && CustomPathManager.FindCitizenPathPosition(targetPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out targetPathPos); if (foundParkPathPos && foundTargetPathPos) { PathUnit.Position dummyPathPos = default(PathUnit.Position); uint pathId; From a61f0ccd8ef128e29003d8f6e7e6ad17eec9b173 Mon Sep 17 00:00:00 2001 From: Victor-Philipp Negoescu Date: Fri, 7 Jun 2019 22:27:32 +0200 Subject: [PATCH 022/142] #259: corrected junction check at transition nodes --- TLM/TLM/Manager/Impl/RoutingManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Manager/Impl/RoutingManager.cs b/TLM/TLM/Manager/Impl/RoutingManager.cs index f5d0ffc78..9a7a72751 100644 --- a/TLM/TLM/Manager/Impl/RoutingManager.cs +++ b/TLM/TLM/Manager/Impl/RoutingManager.cs @@ -595,7 +595,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui (nextIncomingDir == ArrowDirection.Right && hasLeftArrow) || // valid incoming right (nextIncomingDir == ArrowDirection.Left && hasRightArrow) || // valid incoming left (nextIncomingDir == ArrowDirection.Forward && hasForwardArrow) || // valid incoming straight - (nextIncomingDir == ArrowDirection.Turn && (nextIsSimpleJunction || nextIsEndOrOneWayOut || ((leftHandDrive && hasRightArrow) || (!leftHandDrive && hasLeftArrow))))) { // valid turning lane + (nextIncomingDir == ArrowDirection.Turn && (!isNextRealJunction || nextIsEndOrOneWayOut || ((leftHandDrive && hasRightArrow) || (!leftHandDrive && hasLeftArrow))))) { // valid turning lane #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane arrow check passed for nextLaneId={nextLaneId}, idx={nextLaneIndex}. adding as default lane."); From bb85e627db9d7337236ae1d521046025faecaa84 Mon Sep 17 00:00:00 2001 From: Victor-Philipp Negoescu Date: Fri, 7 Jun 2019 23:11:08 +0200 Subject: [PATCH 023/142] - Fixed minor issues regarding saving/loading junction restrictions - Bugfix: Changes of default junction restrictions are not reflected in the UI overlay - Bugfix: Resetting stuck cims unpauses the simulation (#351) --- .../Manager/Impl/JunctionRestrictionsManager.cs | 6 +++--- TLM/TLM/Manager/Impl/UtilityManager.cs | 14 ++++++++++---- TLM/TLM/State/Options.cs | 7 ++++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs index af6af352b..600ac3792 100644 --- a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs @@ -707,7 +707,7 @@ public bool LoadData(List data) { SetNearTurnOnRedAllowed(segNodeConf.segmentId, true, (bool)flags.turnOnRedAllowed); } - if (flags.farTurnOnRedAllowed != null && IsNearTurnOnRedAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { + if (flags.farTurnOnRedAllowed != null && IsFarTurnOnRedAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { SetFarTurnOnRedAllowed(segNodeConf.segmentId, true, (bool)flags.farTurnOnRedAllowed); } @@ -752,11 +752,11 @@ public bool LoadData(List data) { SetPedestrianCrossingAllowed(segNodeConf.segmentId, false, (bool)flags.pedestrianCrossingAllowed); } - if (flags.turnOnRedAllowed != null) { + if (flags.turnOnRedAllowed != null && IsNearTurnOnRedAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { SetNearTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.turnOnRedAllowed); } - if (flags.farTurnOnRedAllowed != null) { + if (flags.farTurnOnRedAllowed != null && IsFarTurnOnRedAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { SetFarTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.farTurnOnRedAllowed); } return true; diff --git a/TLM/TLM/Manager/Impl/UtilityManager.cs b/TLM/TLM/Manager/Impl/UtilityManager.cs index abdd79e42..17e7e135a 100644 --- a/TLM/TLM/Manager/Impl/UtilityManager.cs +++ b/TLM/TLM/Manager/Impl/UtilityManager.cs @@ -93,8 +93,12 @@ public void PrintAllDebugInfo() { public void ResetStuckEntities() { Log.Info($"UtilityManager.RemoveStuckEntities() called."); - Log.Info($"UtilityManager.RemoveStuckEntities(): Pausing simulation."); - Singleton.instance.ForcedSimulationPaused = true; + bool wasPaused = Singleton.instance.ForcedSimulationPaused; + + if (!wasPaused) { + Log.Info($"UtilityManager.RemoveStuckEntities(): Pausing simulation."); + Singleton.instance.ForcedSimulationPaused = true; + } Log.Info($"UtilityManager.RemoveStuckEntities(): Waiting for all paths."); Singleton.instance.WaitForAllPaths(); @@ -150,8 +154,10 @@ public void ResetStuckEntities() { } } - Log.Info($"UtilityManager.RemoveStuckEntities(): Unpausing simulation."); - Singleton.instance.ForcedSimulationPaused = false; + if (!wasPaused) { + Log.Info($"UtilityManager.RemoveStuckEntities(): Unpausing simulation."); + Singleton.instance.ForcedSimulationPaused = false; + } } } } diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index c79cc80b1..c295078b3 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -947,7 +947,7 @@ private static void onAllowLaneChangesWhileGoingStraightChanged(bool newValue) { } Log._Debug($"allowLaneChangesWhileGoingStraight changed to {newValue}"); - allowLaneChangesWhileGoingStraight = newValue; + setAllowLaneChangesWhileGoingStraight(newValue); } private static void onTrafficLightPriorityRulesChanged(bool newValue) { @@ -1298,6 +1298,7 @@ public static void setAllowUTurns(bool value) { if (allowUTurnsToggle != null) allowUTurnsToggle.isChecked = value; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setAllowNearTurnOnRed(bool newValue) { @@ -1305,6 +1306,7 @@ public static void setAllowNearTurnOnRed(bool newValue) { if (allowNearTurnOnRedToggle != null) allowNearTurnOnRedToggle.isChecked = newValue; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setAllowFarTurnOnRed(bool newValue) { @@ -1312,6 +1314,7 @@ public static void setAllowFarTurnOnRed(bool newValue) { if (allowFarTurnOnRedToggle != null) allowFarTurnOnRedToggle.isChecked = newValue; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setAllowLaneChangesWhileGoingStraight(bool value) { @@ -1319,6 +1322,7 @@ public static void setAllowLaneChangesWhileGoingStraight(bool value) { if (allowLaneChangesWhileGoingStraightToggle != null) allowLaneChangesWhileGoingStraightToggle.isChecked = value; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setAllowEnterBlockedJunctions(bool value) { @@ -1326,6 +1330,7 @@ public static void setAllowEnterBlockedJunctions(bool value) { if (allowEnterBlockedJunctionsToggle != null) allowEnterBlockedJunctionsToggle.isChecked = value; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setTrafficLightPriorityRules(bool value) { From c99d4da02ca7b13988fa5c465e1a24f15b21aa90 Mon Sep 17 00:00:00 2001 From: dymanoid <9433345+dymanoid@users.noreply.github.com> Date: Sat, 8 Jun 2019 03:25:55 +0200 Subject: [PATCH 024/142] Use actual array size instead of fixed MAX_VEHICLE_COUNT constant --- TLM/TLM/Custom/Data/CustomVehicle.cs | 2 +- TLM/TLM/Custom/Manager/CustomVehicleManager.cs | 2 +- TLM/TLM/Manager/Impl/UtilityManager.cs | 4 ++-- TLM/TLM/Manager/Impl/VehicleStateManager.cs | 10 +++------- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/TLM/TLM/Custom/Data/CustomVehicle.cs b/TLM/TLM/Custom/Data/CustomVehicle.cs index 36fd3aec6..d0de5d7fa 100644 --- a/TLM/TLM/Custom/Data/CustomVehicle.cs +++ b/TLM/TLM/Custom/Data/CustomVehicle.cs @@ -28,7 +28,7 @@ public static void Spawn(ref Vehicle vehicleData, ushort vehicleId) { while (trailingVehicle != 0) { vehManager.m_vehicles.m_buffer[trailingVehicle].Spawn(trailingVehicle); trailingVehicle = vehManager.m_vehicles.m_buffer[trailingVehicle].m_trailingVehicle; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > vehManager.m_vehicles.m_buffer.Length) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } diff --git a/TLM/TLM/Custom/Manager/CustomVehicleManager.cs b/TLM/TLM/Custom/Manager/CustomVehicleManager.cs index 392a6ce0e..911e60564 100644 --- a/TLM/TLM/Custom/Manager/CustomVehicleManager.cs +++ b/TLM/TLM/Custom/Manager/CustomVehicleManager.cs @@ -31,7 +31,7 @@ public bool CustomCreateVehicle(out ushort vehicleId, ref Randomizer r, VehicleI #if BENCHMARK using (var bm = new Benchmark(null, "keep-spare-vehicles")) { #endif - if (this.m_vehicleCount > VehicleManager.MAX_VEHICLE_COUNT - 5) { + if (this.m_vehicleCount > m_vehicles.m_buffer.Length - 5) { // prioritize service vehicles and public transport when hitting the vehicle limit ItemClass.Service service = info.GetService(); if (service == ItemClass.Service.Residential || service == ItemClass.Service.Industrial || service == ItemClass.Service.Commercial || service == ItemClass.Service.Office) { diff --git a/TLM/TLM/Manager/Impl/UtilityManager.cs b/TLM/TLM/Manager/Impl/UtilityManager.cs index abdd79e42..adfb01fa8 100644 --- a/TLM/TLM/Manager/Impl/UtilityManager.cs +++ b/TLM/TLM/Manager/Impl/UtilityManager.cs @@ -123,7 +123,7 @@ public void ResetStuckEntities() { } Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting vehicles that are waiting for a path."); - for (uint vehicleId = 1; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { + for (uint vehicleId = 1; vehicleId < VehicleManager.instance.m_vehicles.m_buffer.Length; ++vehicleId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing vehicle {vehicleId}."); if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.WaitingPath) != 0) { if (Singleton.instance.m_vehicles.m_buffer[vehicleId].m_path != 0u) { @@ -137,7 +137,7 @@ public void ResetStuckEntities() { } Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting vehicles that are parking and where no parked vehicle is assigned to the driver."); - for (uint vehicleId = 1; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { + for (uint vehicleId = 1; vehicleId < VehicleManager.instance.m_vehicles.m_buffer.Length; ++vehicleId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing vehicle {vehicleId}."); if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.Parking) != 0) { ushort driverInstanceId = CustomPassengerCarAI.GetDriverInstanceId((ushort)vehicleId, ref Singleton.instance.m_vehicles.m_buffer[vehicleId]); diff --git a/TLM/TLM/Manager/Impl/VehicleStateManager.cs b/TLM/TLM/Manager/Impl/VehicleStateManager.cs index 1dfaf04a5..5c04fb5cf 100644 --- a/TLM/TLM/Manager/Impl/VehicleStateManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleStateManager.cs @@ -21,10 +21,6 @@ public class VehicleStateManager : AbstractCustomManager, IVehicleStateManager { /// public VehicleState[] VehicleStates { get; private set; } = null; - static VehicleStateManager() { - Instance = new VehicleStateManager(); - } - protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Vehicle states:"); @@ -37,8 +33,8 @@ protected override void InternalPrintDebugInfo() { } private VehicleStateManager() { - VehicleStates = new VehicleState[VehicleManager.MAX_VEHICLE_COUNT]; - for (uint i = 0; i < VehicleManager.MAX_VEHICLE_COUNT; ++i) { + VehicleStates = new VehicleState[VehicleManager.instance.m_vehicles.m_buffer.Length]; + for (uint i = 0; i < VehicleStates.Length; ++i) { VehicleStates[i] = new VehicleState((ushort)i); } } @@ -253,7 +249,7 @@ internal void InitAllVehicles() { Log._Debug("VehicleStateManager: InitAllVehicles()"); VehicleManager vehicleManager = Singleton.instance; - for (uint vehicleId = 0; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { + for (uint vehicleId = 0; vehicleId < VehicleManager.instance.m_vehicles.m_buffer.Length; ++vehicleId) { Services.VehicleService.ProcessVehicle((ushort)vehicleId, delegate (ushort vId, ref Vehicle vehicle) { if ((vehicle.m_flags & Vehicle.Flags.Created) == 0) { return true; From 84384e9ab96bba70e89096646b5b572c086c6dda Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 00:04:19 +0100 Subject: [PATCH 025/142] Translate `Delete` and `Unsibscribe` buttons There was already locale key for `Delete` so only new key is for `Unsubscribe` --- TLM/TLM/Resources/lang.txt | 3 ++- TLM/TLM/Resources/lang_de.txt | 3 ++- TLM/TLM/Resources/lang_es.txt | 3 ++- TLM/TLM/Resources/lang_fr.txt | 1 + TLM/TLM/Resources/lang_it.txt | 3 ++- TLM/TLM/Resources/lang_ja.txt | 3 ++- TLM/TLM/Resources/lang_ko.txt | 1 + TLM/TLM/Resources/lang_nl.txt | 3 ++- TLM/TLM/Resources/lang_pl.txt | 3 ++- TLM/TLM/Resources/lang_pt.txt | 3 ++- TLM/TLM/Resources/lang_ru.txt | 3 ++- TLM/TLM/Resources/lang_zh-tw.txt | 3 ++- TLM/TLM/Resources/lang_zh.txt | 3 ++- TLM/TLM/UI/IncompatibleModsPanel.cs | 2 +- 14 files changed, 25 insertions(+), 12 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 43dbf6607..138db4c10 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index e46f5123b..9fad4a615 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Gilt auch für Links- & R Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 56ce3bea1..b3a7f74f1 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index fd08dfda0..08e3861a9 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -232,3 +232,4 @@ Scan_for_known_incompatible_mods_on_startup Rechercher les mods incompatibles au Ignore_disabled_mods Ignorer les mods désactivés Traffic_Manager_detected_incompatible_mods a détecté des mods incompatibles Notify_me_if_there_is_an_unexpected_mod_conflict Afficher un message d'erreur si un mod incompatible est détecté +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index aaed10fd6..62456b730 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index a8fda7bd7..9768762e1 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets 一方通行路の左右 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index ada9b8e30..48ea474f7 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -232,3 +232,4 @@ Scan_for_known_incompatible_mods_on_startup 게임 실행시 비호환되는 모 Ignore_disabled_mods 비활성화된 모드 무시하기 Traffic_Manager_detected_incompatible_mods 와 비호환되는 모드 감지 Notify_me_if_there_is_an_unexpected_mod_conflict 모드와 비 호환되는 모드 발견 시 에러 보여주기 +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 03dd6284a..857f9a6e5 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index dc4bf6b9c..bd3a9a5b0 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Uwzględnia również skr Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie gry Ignore_disabled_mods Ignoruj wyłączone mody Traffic_Manager_detected_incompatible_mods wykrył niekompatybilne mody -Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index c2174f6b3..ae7076489 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 53e4aa025..5fd1b1024 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Разрешить нап Scan_for_known_incompatible_mods_on_startup Сканирование известных несовместимых модов при запуске Ignore_disabled_mods Игнорировать отключённые моды Traffic_Manager_detected_incompatible_mods обнаружил несовместимые моды -Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index 57d470201..0223c64b2 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index b70262511..56669b699 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -231,4 +231,5 @@ Also_apply_to_left/right_turns_between_one-way_streets 单行道左右转也适 Scan_for_known_incompatible_mods_on_startup 启动时检测是否存在已知不兼容MOD Ignore_disabled_mods 忽略未启用MOD Traffic_Manager_detected_incompatible_mods 检测到的不兼容MOD -Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 +Unsubscribe Unsubscribe \ No newline at end of file diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index d58263043..419bc415c 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -173,7 +173,7 @@ private void CloseButtonClick(UIComponent component, UIMouseEventParameter event private UIPanel CreateEntry(ref UIScrollablePanel parent, string modName, PluginInfo mod) { - string caption = mod.publishedFileID.AsUInt64 == LOCAL_MOD ? "Delete" : "Unsubscribe"; + string caption = mod.publishedFileID.AsUInt64 == LOCAL_MOD ? Translation.GetString("Delete") : Translation.GetString("Unsubscribe"); UIPanel panel = parent.AddUIComponent(); panel.size = new Vector2(560, 50); From 26d55d1d9e3392305bcd40d6cf05163f72b178f4 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 01:19:21 +0100 Subject: [PATCH 026/142] Remove panel when unsubscribing First attempt at removing panel when a mod is removed. Also, if no mods left, the dialog closes. --- TLM/TLM/UI/IncompatibleModsPanel.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index 419bc415c..c89cb028f 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -87,17 +87,16 @@ public void Initialize() scrollablePanel.size = new Vector2(550, 340); scrollablePanel.relativePosition = new Vector3(0, 0); scrollablePanel.clipChildren = true; + scrollablePanel.autoLayoutStart = LayoutStart.TopLeft; + scrollablePanel.autoLayoutDirection = LayoutDirection.Vertical; + scrollablePanel.autoLayout = true; if (IncompatibleMods.Count != 0) { - int acc = 0; UIPanel item; IncompatibleMods.ForEach((pair) => { item = CreateEntry(ref scrollablePanel, pair.Value, pair.Key); - item.relativePosition = new Vector2(0, acc); - item.size = new Vector2(560, 50); - acc += 50; }); item = null; } @@ -163,12 +162,17 @@ private void RunModsCheckerOnStartup_eventCheckChanged(bool value) } private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) + { + CloseDialog(); + eventparam.Use(); + } + + private void CloseDialog() { closeButton.eventClick -= CloseButtonClick; TryPopModal(); Hide(); Unfocus(); - eventparam.Use(); } private UIPanel CreateEntry(ref UIScrollablePanel parent, string modName, PluginInfo mod) @@ -211,7 +215,12 @@ private void UnsubscribeClick(UIComponent component, UIMouseEventParameter event { IncompatibleMods.Remove(mod); component.parent.Disable(); - component.isVisible = false; + component.parent.isVisible = false; + + if (IncompatibleMods.Count == 0) + { + CloseDialog(); + } } else { From 4e8c8ddc63374ab7518ebea83d814c82050c8c62 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 01:58:28 +0100 Subject: [PATCH 027/142] Detect offline or --noWorkshop states When offline, the LABS and STABLE builds won't check for local TM:PE mods. This prevents offline LABS/STABLE builds from deleting themselves lol. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 42 ++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 115215b7d..d9516b0ff 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -1,4 +1,5 @@ using ColossalFramework; +using ColossalFramework.PlatformServices; // used in RELEASE builds using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; @@ -6,7 +7,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; +using System.Linq; // used in RELEASE builds using System.Reflection; using TrafficManager.UI; using static ColossalFramework.Plugins.PluginManager; @@ -62,6 +63,10 @@ public Dictionary ScanForIncompatibleMods() // only check enabled mods? bool filterToEnabled = State.GlobalConfig.Instance.Main.IgnoreDisabledMods; +#if !DEBUG + bool offline = IsOffline(); +#endif + // iterate plugins foreach (PluginInfo mod in Singleton.instance.GetPluginsInfo()) { @@ -76,7 +81,7 @@ public Dictionary ScanForIncompatibleMods() } #if !DEBUG // Workshop TM:PE builds treat local builds as incompatible - else if (mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) + else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) { Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); string folder = mod.modPath.Split(Path.DirectorySeparatorChar).Last(); @@ -111,6 +116,39 @@ public string GetModName(PluginInfo plugin) return name; } + /// + /// Works out if the game is effectively running in offline mode, in which no workshop mod subscriptions will be active. + /// + /// Applicalbe "offline" states include: + /// + /// * Origin plaform service (no support for Steam workshop) + /// * Steam (or other platform service) not active + /// * --noWorkshop launch option + /// + /// This is allows LABS and STABLE builds to be used offline without trying to delete themselves. + /// + /// + /// Returns true if game is offline for any reason, otherwise false. +#if !DEBUG + private bool IsOffline() + { + // TODO: Work out if TGP and QQGame platform services allow workshop + if (PlatformService.platformType == PlatformType.Origin) + { + return true; + } + else if (!PlatformService.active) + { + return true; + } + else if (PluginManager.noWorkshop) + { + return true; + } + return false; + } +#endif + /// /// Loads and parses the incompatible_mods.txt resource, adds other workshop branches of TM:PE as applicable. /// From 57dfc56f8a1a19730f4b64d4d82649977ebb294a Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 02:14:54 +0100 Subject: [PATCH 028/142] Improved offline detection Assuming any form of offline will cause `PluginManager.noWorkshop` to be `true`, check that first. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index d9516b0ff..c1aa9f836 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -133,15 +133,15 @@ public string GetModName(PluginInfo plugin) private bool IsOffline() { // TODO: Work out if TGP and QQGame platform services allow workshop - if (PlatformService.platformType == PlatformType.Origin) + if (PluginManager.noWorkshop) { return true; } - else if (!PlatformService.active) + else if (PlatformService.platformType == PlatformType.Origin) { return true; } - else if (PluginManager.noWorkshop) + else if (!PlatformService.active) { return true; } From 9ebe88cc66e92ba664bcc91f0b83c6f9425f3a00 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 19:18:34 +0100 Subject: [PATCH 029/142] Code cleanup and comments --- TLM/TLM/UI/IncompatibleModsPanel.cs | 79 +++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index c89cb028f..d8418e93c 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -24,8 +24,16 @@ public class IncompatibleModsPanel : UIPanel private UICheckBox runModsCheckerOnStartup; private UIComponent blurEffect; + /// + /// List of incompatible mods from . + /// public Dictionary IncompatibleMods { get; set; } + /// + /// Initialises the dialog, populates it with list of incompatible mods, and adds it to the modal stack. + /// + /// If the modal stack was previously empty, a blur effect is added over the screen background. + /// public void Initialize() { Log._Debug("IncompatibleModsPanel initialize"); @@ -91,14 +99,13 @@ public void Initialize() scrollablePanel.autoLayoutDirection = LayoutDirection.Vertical; scrollablePanel.autoLayout = true; + // Populate list of incompatible mods if (IncompatibleMods.Count != 0) { - UIPanel item; IncompatibleMods.ForEach((pair) => { - item = CreateEntry(ref scrollablePanel, pair.Value, pair.Key); + CreateEntry(ref scrollablePanel, pair.Value, pair.Key); }); - item = null; } scrollablePanel.FitTo(panel); @@ -128,6 +135,7 @@ public void Initialize() thumb.relativePosition = Vector3.zero; verticalScroll.thumbObject = thumb; + // Add blur effect if applicable blurEffect = GameObject.Find("ModalEffect").GetComponent(); AttachUIComponent(blurEffect.gameObject); blurEffect.size = new Vector2(resolution.x, resolution.y); @@ -139,9 +147,15 @@ public void Initialize() ValueAnimator.Animate("ModalEffect", delegate(float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); } + // Make sure modal dialog is in front of all other UI BringToFront(); } + /// + /// Allows the user to press "Esc" to close the dialog. + /// + /// + /// Details about the key press. protected override void OnKeyDown(UIKeyEventParameter p) { if (Input.GetKey(KeyCode.Escape) || Input.GetKey(KeyCode.Return)) @@ -155,18 +169,32 @@ protected override void OnKeyDown(UIKeyEventParameter p) base.OnKeyDown(p); } + /// + /// Hnadles click of the "Run incompatible check on startup" checkbox and updates game options accordingly. + /// + /// + /// The new value of the checkbox; true if checked, otherwise false. private void RunModsCheckerOnStartup_eventCheckChanged(bool value) { Log._Debug("Incompatible mods checker run on game launch changed to " + value); State.Options.setScanForKnownIncompatibleMods(value); } + /// + /// Handles click of the "close dialog" button; pops the dialog off the modal stack. + /// + /// + /// Handle to the close button UI component (not used). + /// Details about the click event. private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) { CloseDialog(); eventparam.Use(); } + /// + /// Pops the popup dialog off the modal stack. + /// private void CloseDialog() { closeButton.eventClick -= CloseButtonClick; @@ -175,7 +203,14 @@ private void CloseDialog() Unfocus(); } - private UIPanel CreateEntry(ref UIScrollablePanel parent, string modName, PluginInfo mod) + /// + /// Creates a panel representing the mod and adds it to the UI component. + /// + /// + /// The parent UI component that the panel will be added to. + /// The name of the mod, which is displayed to user. + /// The instance of the incompatible mod. + private void CreateEntry(ref UIScrollablePanel parent, string modName, PluginInfo mod) { string caption = mod.publishedFileID.AsUInt64 == LOCAL_MOD ? Translation.GetString("Delete") : Translation.GetString("Unsubscribe"); @@ -189,13 +224,20 @@ private UIPanel CreateEntry(ref UIScrollablePanel parent, string modName, Plugin label.relativePosition = new Vector2(10, 15); CreateButton(panel, caption, (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, mod); }); - - return panel; } + /// + /// Handles click of "Unsubscribe" or "Delete" button; removes the associated mod and updates UI. + /// + /// Once all incompatible mods are removed, the dialog will be closed automatically. + /// + /// + /// A handle to the UI button that was clicked. + /// Details of the click event. + /// The instance of the mod to remove. private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, PluginInfo mod) { - + eventparam.Use(); bool success = false; // disable button to prevent accidental clicks @@ -229,6 +271,13 @@ private void UnsubscribeClick(UIComponent component, UIMouseEventParameter event } } + /// + /// Deletes a locally installed TM:PE mod. + /// + /// + /// The associated with the mod that needs deleting. + /// + /// Returns true if successfully deleted, otherwise false. private bool DeleteLocalTMPE(PluginInfo mod) { try @@ -244,7 +293,16 @@ private bool DeleteLocalTMPE(PluginInfo mod) } } - private UIButton CreateButton(UIComponent parent, string text, int x, int y, MouseEventHandler eventClick) + /// + /// Creates an `Unsubscribe` or `Delete` button (as applicable to mod location) and attaches it to the UI component. + /// + /// + /// The parent UI component which the button will be attached to. + /// The translated text to display on the button. + /// The x position of the top-left corner of the button, relative to . + /// The y position of the top-left corner of the button, relative to . + /// The event handler for when the button is clicked. + private void CreateButton(UIComponent parent, string text, int x, int y, MouseEventHandler eventClick) { var button = parent.AddUIComponent(); button.textScale = 0.8f; @@ -260,10 +318,11 @@ private UIButton CreateButton(UIComponent parent, string text, int x, int y, Mou button.text = text; button.relativePosition = new Vector3(x, y); button.eventClick += eventClick; - - return button; } + /// + /// Pops the dialog from the modal stack. If no more modal dialogs are present, the background blur effect is also removed. + /// private void TryPopModal() { if (UIView.HasModalInput()) From 7018932394f5b7d87f54b59172aa59fafd6b5730 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 19:25:23 +0100 Subject: [PATCH 030/142] Code clean up and comments. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index c1aa9f836..781912f6c 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -31,6 +31,9 @@ public ModsCompatibilityChecker() incompatibleMods = LoadListOfIncompatibleMods(); } + /// + /// Initiates scan for incompatible mods. If any found, and the user has enabled the mod checker, it creates and initialises the modal dialog panel. + /// public void PerformModCheck() { Dictionary detected = ScanForIncompatibleMods(); From 6ea6d352def56b89932e5d7d18c56f3fccaf1043 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 19:29:35 +0100 Subject: [PATCH 031/142] Reverted to just hiding the button, rather than whole panel --- TLM/TLM/UI/IncompatibleModsPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index d8418e93c..0e0a80bdd 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -257,8 +257,9 @@ private void UnsubscribeClick(UIComponent component, UIMouseEventParameter event { IncompatibleMods.Remove(mod); component.parent.Disable(); - component.parent.isVisible = false; + component.isVisible = false; + // automatically close the dialog if no more mods to remove if (IncompatibleMods.Count == 0) { CloseDialog(); From daf55a8c794cba4ab84ac176f3c88c6320e13886 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 19:52:03 +0100 Subject: [PATCH 032/142] Remove TMPE.GlobalConfigGeberator project Fixes #367 --- TLM/TMPE.GlobalConfigGenerator/App.config | 6 -- TLM/TMPE.GlobalConfigGenerator/Generator.cs | 56 ---------- .../Properties/AssemblyInfo.cs | 35 ------ .../TMPE.GlobalConfigGenerator.csproj | 101 ------------------ .../packages.config | 4 - TLM/TMPE.sln | 22 +--- 6 files changed, 5 insertions(+), 219 deletions(-) delete mode 100644 TLM/TMPE.GlobalConfigGenerator/App.config delete mode 100644 TLM/TMPE.GlobalConfigGenerator/Generator.cs delete mode 100644 TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs delete mode 100644 TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj delete mode 100644 TLM/TMPE.GlobalConfigGenerator/packages.config diff --git a/TLM/TMPE.GlobalConfigGenerator/App.config b/TLM/TMPE.GlobalConfigGenerator/App.config deleted file mode 100644 index 343984d02..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/TLM/TMPE.GlobalConfigGenerator/Generator.cs b/TLM/TMPE.GlobalConfigGenerator/Generator.cs deleted file mode 100644 index 295af4079..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/Generator.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using System.Xml.Serialization; -using TrafficManager.State; - -namespace GlobalConfigGenerator { - class Generator { - public const string FILENAME = "TMPE_GlobalConfig.xml"; - public static int? RushHourParkingSearchRadius { get; private set; } = null; - //private static DateTime? rushHourConfigModifiedTime = null; - private const string RUSHHOUR_CONFIG_FILENAME = "RushHourOptions.xml"; - //private static uint lastRushHourConfigCheck = 0; - - public static void Main(string[] args) { - /*WriteDefaultConfig(); - GlobalConfig conf = LoadConfig();*/ - TestRushHourConf(); - Console.ReadLine(); - } - - /*public static void WriteDefaultConfig() { - GlobalConfig conf = new GlobalConfig(); - XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); - TextWriter writer = new StreamWriter(FILENAME); - serializer.Serialize(writer, conf); - writer.Close(); - } - - public static GlobalConfig LoadConfig() { - FileStream fs = new FileStream(FILENAME, FileMode.Open); - XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); - GlobalConfig conf = (GlobalConfig)serializer.Deserialize(fs); - Console.WriteLine("OK"); - return conf; - }*/ - - private static void TestRushHourConf() { // TODO refactor - XmlDocument doc = new XmlDocument(); - doc.Load(RUSHHOUR_CONFIG_FILENAME); - XmlNode root = doc.DocumentElement; - - XmlNode betterParkingNode = root.SelectSingleNode("OptionPanel/data/BetterParking"); - XmlNode parkingSpaceRadiusNode = root.SelectSingleNode("OptionPanel/data/ParkingSearchRadius"); - - string s = betterParkingNode.InnerText; - - if ("True".Equals(s)) { - RushHourParkingSearchRadius = int.Parse(parkingSpaceRadiusNode.InnerText); - } - } - } -} diff --git a/TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs b/TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs deleted file mode 100644 index 04ca5b3d7..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// Allgemeine Informationen über eine Assembly werden über die folgenden -// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, -// die einer Assembly zugeordnet sind. -[assembly: AssemblyTitle("GlobalConfigGenerator")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("GlobalConfigGenerator")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar -// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von -// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. -[assembly: ComVisible(false)] - -// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird -[assembly: Guid("48d1868b-ee81-4339-95c9-4c5eadeb9eca")] - -// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: -// -// Hauptversion -// Nebenversion -// Buildnummer -// Revision -// -// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern -// übernehmen, indem Sie "*" eingeben: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.*")] \ No newline at end of file diff --git a/TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj b/TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj deleted file mode 100644 index f26d62478..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - - Debug - AnyCPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA} - Exe - Properties - GlobalConfigGenerator - TMPE.GlobalConfigGenerator - v3.5 - 512 - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - ..\TMPE.ruleset - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - ..\TMPE.ruleset - - - bin\QueuedStats\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - ..\TMPE.ruleset - - - true - bin\Benchmark\ - DEBUG;TRACE - full - AnyCPU - prompt - ..\TMPE.ruleset - - - true - bin\PF2_Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - ..\TMPE.ruleset - - - - - - - - - - - - - - - - - - - - {7422AE58-8B0A-401C-9404-F4A438EFFE10} - TLM - - - - - - - - - \ No newline at end of file diff --git a/TLM/TMPE.GlobalConfigGenerator/packages.config b/TLM/TMPE.GlobalConfigGenerator/packages.config deleted file mode 100644 index 947d6d05b..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TLM/TMPE.sln b/TLM/TMPE.sln index 005f07cc1..547ad9f87 100644 --- a/TLM/TMPE.sln +++ b/TLM/TMPE.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.539 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TLM", "TLM\TLM.csproj", "{7422AE58-8B0A-401C-9404-F4A438EFFE10}" ProjectSection(ProjectDependencies) = postProject @@ -16,11 +16,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.GlobalConfigGenerator", "TMPE.GlobalConfigGenerator\TMPE.GlobalConfigGenerator.csproj", "{48D1868B-EE81-4339-95C9-4C5EADEB9ECA}" - ProjectSection(ProjectDependencies) = postProject - {7422AE58-8B0A-401C-9404-F4A438EFFE10} = {7422AE58-8B0A-401C-9404-F4A438EFFE10} - EndProjectSection -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.UnitTest", "TMPE.UnitTest\TMPE.UnitTest.csproj", "{D0D1848A-9BAE-4121-89A0-66757D16BC73}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.GenericGameBridge", "TMPE.GenericGameBridge\TMPE.GenericGameBridge.csproj", "{663B991F-32A1-46E1-A4D3-540F8EA7F386}" @@ -57,16 +52,6 @@ Global {7422AE58-8B0A-401C-9404-F4A438EFFE10}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.ActiveCfg = Release|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.Build.0 = Release|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.Build.0 = Release|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -141,4 +126,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CFCFBDC9-2706-483F-B4A3-F810E5CDE489} + EndGlobalSection EndGlobal From 1c6e0a163ab3314f965eeb1ee2ac8ee0b364eb10 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 10 Jun 2019 21:01:21 +0100 Subject: [PATCH 033/142] Update lang_zh.txt Chinese translation update from @Emphasia Updates #336, #334 --- TLM/TLM/Resources/lang_zh.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index fc8cd7f9f..a65dc8ff7 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -1,4 +1,4 @@ -Switch_traffic_lights 红绿灯增删 +Switch_traffic_lights 红绿灯增删 Add_priority_signs 添加路口优先权 Manual_traffic_lights 手动控制红绿灯 Timed_traffic_lights 红绿灯定时设置 @@ -150,7 +150,7 @@ Reset_global_configuration 重置所有设定 General 常规 Gameplay 游戏 Overlays 覆盖 -Individual_driving_styles Individual driving styles +Individual_driving_styles 个人驾驶风格 Evacuation_busses_may_ignore_traffic_rules 应急疏散巴士可忽略交通规则 Evacuation_busses_may_only_be_used_to_reach_a_shelter 应急疏散巴士仅可用于到达避难所 Vehicle_behavior 车辆行为 From fc3447b9f69e4df3b4aaa0d49dc983458c7790b4 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 13 Jun 2019 13:56:12 +0100 Subject: [PATCH 034/142] Update TMPE.sln Fixed merge conflict --- TLM/TMPE.sln | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/TLM/TMPE.sln b/TLM/TMPE.sln index a8c3748f0..678c6ee09 100644 --- a/TLM/TMPE.sln +++ b/TLM/TMPE.sln @@ -16,11 +16,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.GlobalConfigGenerator", "TMPE.GlobalConfigGenerator\TMPE.GlobalConfigGenerator.csproj", "{48D1868B-EE81-4339-95C9-4C5EADEB9ECA}" - ProjectSection(ProjectDependencies) = postProject - {7422AE58-8B0A-401C-9404-F4A438EFFE10} = {7422AE58-8B0A-401C-9404-F4A438EFFE10} - EndProjectSection -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.UnitTest", "TMPE.UnitTest\TMPE.UnitTest.csproj", "{D0D1848A-9BAE-4121-89A0-66757D16BC73}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.GenericGameBridge", "TMPE.GenericGameBridge\TMPE.GenericGameBridge.csproj", "{663B991F-32A1-46E1-A4D3-540F8EA7F386}" @@ -60,18 +55,6 @@ Global {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release LABS|Any CPU.Build.0 = Release LABS|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.ActiveCfg = Release|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.Build.0 = Release|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release LABS|Any CPU.ActiveCfg = Release|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release LABS|Any CPU.Build.0 = Release|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.Build.0 = Release|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU From 852da65927394d3cbf4c3df386cab5f88f104c0b Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 13 Jun 2019 14:29:24 +0100 Subject: [PATCH 035/142] Update lang_pl.txt --- TLM/TLM/Resources/lang_pl.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 97c15a4b7..28f3c13ad 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -1,4 +1,4 @@ -Switch_traffic_lights Włącznik sygnalizacji +Switch_traffic_lights Włącznik sygnalizacji Add_priority_signs Dodaj znaki pierwszeństwa Manual_traffic_lights Sygnalizacja Ręczna Timed_traffic_lights Sygnalizacja Czasowa @@ -232,4 +232,4 @@ Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie Ignore_disabled_mods Ignoruj wyłączone mody Traffic_Manager_detected_incompatible_mods wykrył niekompatybilne mody Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów -Unsubscribe Unsubscribe \ No newline at end of file +Unsubscribe Odsubskrybuj \ No newline at end of file From ee69f7a85c27065646339e19ebd0251ec534b739 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 16 Jun 2019 02:47:00 +0200 Subject: [PATCH 036/142] GUI settings page for keyboard shortcuts. TMPE main window and 6 most used tools can be used with keyboard --- TLM/TLM/Resources/lang.txt | 11 +- TLM/TLM/State/KeyMapping.cs | 285 ++++++++++++++++++ TLM/TLM/State/Options.cs | 20 +- TLM/TLM/TrafficManagerMod.cs | 2 +- TLM/TLM/UI/LinearSpriteButton.cs | 12 +- .../UI/MainMenu/JunctionRestrictionsButton.cs | 41 +-- TLM/TLM/UI/MainMenu/LaneArrowsButton.cs | 41 +-- TLM/TLM/UI/MainMenu/LaneConnectorButton.cs | 37 +-- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 35 ++- .../UI/MainMenu/ManualTrafficLightsButton.cs | 37 +-- TLM/TLM/UI/MainMenu/PrioritySignsButton.cs | 37 +-- TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs | 37 +-- .../UI/MainMenu/ToggleTrafficLightsButton.cs | 38 +-- TLM/TLM/UI/ToolMode.cs | 40 ++- TLM/TLM/UI/TrafficManagerTool.cs | 3 +- TLM/TLM/UI/UIBase.cs | 5 +- TLM/TLM/UI/UIMainMenuButton.cs | 15 +- 17 files changed, 449 insertions(+), 247 deletions(-) create mode 100644 TLM/TLM/State/KeyMapping.cs diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 68534b7ca..1fc247867 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -59,7 +59,7 @@ All_vehicles_may_ignore_lane_arrows All vehicles may ignore lane arrows Busses_may_ignore_lane_arrows Buses may ignore lane arrows Reckless_driving Reckless driving TMPE_Title Traffic Manager: President Edition -Settings_are_defined_for_each_savegame_separately Settings are defined for each savegame separately +Settings_are_defined_for_each_savegame_separately Settings are defined for each savegame separately. Please open settings while in-game. Simulation_accuracy Simulation accuracy (higher accuracy reduces performance) Enable_highway_specific_lane_merging/splitting_rules Enable highway specific lane merging/splitting rules Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Drivers like to change their lane (only applied if Advanced AI is enabled) @@ -231,4 +231,11 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Keyboard_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keyboard_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keyboard_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keyboard_use_lane_connections_tool Use 'Lane Connections' Tool +Keyboard_use_priority_signs_tool Use 'Priority Signs' Tool +Keyboard_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keyboard_use_speed_limits_tool Use 'Speed Limits' Tool \ No newline at end of file diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs new file mode 100644 index 000000000..1eb0fc73a --- /dev/null +++ b/TLM/TLM/State/KeyMapping.cs @@ -0,0 +1,285 @@ +// Based on keymapping module from CS-MoveIt mod +// Thanks to https://github.com/Quboid/CS-MoveIt + +using System; +using System.Reflection; +using ColossalFramework; +using ColossalFramework.Globalization; +using ColossalFramework.UI; +using CSUtil.Commons; +using TrafficManager.UI; +using UnityEngine; + +namespace TrafficManager.State { + public class OptionsKeymappingMain : OptionsKeymapping { + private void Awake() { + TryCreateConfig(); + AddKeymapping(Translation.GetString("Keyboard_toggle_TMPE_main_menu"), + KeyToggleTMPEMainMenu); + + AddKeymapping(Translation.GetString("Keyboard_toggle_traffic_lights_tool"), + KeyToggleTrafficLightTool); + AddKeymapping(Translation.GetString("Keyboard_use_lane_arrow_tool"), + KeyLaneArrowTool); + AddKeymapping(Translation.GetString("Keyboard_use_lane_connections_tool"), + KeyLaneConnectionsTool); + AddKeymapping(Translation.GetString("Keyboard_use_priority_signs_tool"), + KeyPrioritySignsTool); + AddKeymapping(Translation.GetString("Keyboard_use_junction_restrictions_tool"), + KeyJunctionRestrictionsTool); + AddKeymapping(Translation.GetString("Keyboard_use_speed_limits_tool"), + KeySpeedLimitsTool); + } + } + + public class OptionsKeymapping : UICustomControl { + protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; + private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; + + public static SavedInputKey KeyToggleTMPEMainMenu = + new SavedInputKey("keyToggleTMPEMainMenu", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.T, true, false, true), + true); + + public static SavedInputKey KeyToggleTrafficLightTool = + new SavedInputKey("keyToggleTrafficLightTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.T, true, true, false), + true); + + public static SavedInputKey KeyLaneArrowTool = + new SavedInputKey("keyLaneArrowTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.A, true, true, false), + true); + + public static SavedInputKey KeyLaneConnectionsTool = + new SavedInputKey("keyLaneConnectionsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.C, true, true, false), + true); + + public static SavedInputKey KeyPrioritySignsTool = + new SavedInputKey("keyPrioritySignsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.P, true, true, false), + true); + + public static SavedInputKey KeyJunctionRestrictionsTool = + new SavedInputKey("keyJunctionRestrictionsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.J, true, true, false), + true); + + public static SavedInputKey KeySpeedLimitsTool = + new SavedInputKey("keySpeedLimitsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.S, true, true, false), + true); + + private SavedInputKey editingBinding_; + private string editingBindingCategory_; + + private int count_; + + protected void TryCreateConfig() { + try { + // Creating setting file + if (GameSettings.FindSettingsFileByName(KEYBOARD_SHORTCUTS_FILENAME) == null) { + GameSettings.AddSettingsFile(new SettingsFile + {fileName = KEYBOARD_SHORTCUTS_FILENAME}); + } + } + catch (Exception) { + Log._Debug("Could not load/create the keyboard shortcuts file."); + } + } + + /// + /// Creates a row in the current panel with the label and the button which will prompt user to press a new key. + /// + /// Text to display + /// A SavedInputKey from GlobalConfig.KeyboardShortcuts + protected void AddKeymapping(string label, SavedInputKey savedInputKey) { + var uiPanel = + component.AttachUIComponent(UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as + UIPanel; + if (count_++ % 2 == 1) uiPanel.backgroundSprite = null; + + // Create a label + var uILabel = uiPanel.Find("Name"); + + // Create a button which displays the shortcut and modifies it on click + var uIButton = uiPanel.Find("Binding"); + uIButton.eventKeyDown += OnBindingKeyDown; + uIButton.eventMouseDown += OnBindingMouseDown; + + // Set label text (as provided) and set button text from the SavedInputKey + uILabel.text = label; + uIButton.text = savedInputKey.ToLocalizedString("KEYNAME"); + uIButton.objectUserData = savedInputKey; + } + + protected void OnEnable() { + LocaleManager.eventLocaleChanged += OnLocaleChanged; + } + + protected void OnDisable() { + LocaleManager.eventLocaleChanged -= OnLocaleChanged; + } + + protected void OnLocaleChanged() { + RefreshBindableInputs(); + } + + protected bool IsModifierKey(KeyCode code) { + return code == KeyCode.LeftControl || code == KeyCode.RightControl || + code == KeyCode.LeftShift || code == KeyCode.RightShift || code == KeyCode.LeftAlt || + code == KeyCode.RightAlt; + } + + protected bool IsControlDown() { + return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + } + + protected bool IsShiftDown() { + return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + } + + protected bool IsAltDown() { + return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); + } + + protected bool IsUnbindableMouseButton(UIMouseButton code) { + return code == UIMouseButton.Left || code == UIMouseButton.Right; + } + + protected KeyCode ButtonToKeycode(UIMouseButton button) { + if (button == UIMouseButton.Left) { + return KeyCode.Mouse0; + } + + if (button == UIMouseButton.Right) { + return KeyCode.Mouse1; + } + + if (button == UIMouseButton.Middle) { + return KeyCode.Mouse2; + } + + if (button == UIMouseButton.Special0) { + return KeyCode.Mouse3; + } + + if (button == UIMouseButton.Special1) { + return KeyCode.Mouse4; + } + + if (button == UIMouseButton.Special2) { + return KeyCode.Mouse5; + } + + if (button == UIMouseButton.Special3) { + return KeyCode.Mouse6; + } + + return KeyCode.None; + } + + protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { + // This will only work if the user clicked the modify button + // otherwise no effect + if (editingBinding_ != null && !IsModifierKey(p.keycode)) { + p.Use(); + UIView.PopModal(); + var keycode = p.keycode; + var inputKey = (p.keycode == KeyCode.Escape) + ? editingBinding_.value + : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); + if (p.keycode == KeyCode.Backspace) { + inputKey = SavedInputKey.Empty; + } + + editingBinding_.value = inputKey; + var uITextComponent = p.source as UITextComponent; + uITextComponent.text = editingBinding_.ToLocalizedString("KEYNAME"); + editingBinding_ = null; + editingBindingCategory_ = string.Empty; + } + } + + protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { + // This will only work if the user is not in the process of changing the shortcut + if (editingBinding_ == null) { + p.Use(); + editingBinding_ = (SavedInputKey) p.source.objectUserData; + editingBindingCategory_ = p.source.stringUserData; + var uIButton = p.source as UIButton; + uIButton.buttonsMask = + UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | + UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | + UIMouseButton.Special3; + uIButton.text = "Press any key"; + p.source.Focus(); + UIView.PushModal(p.source); + } else if (!IsUnbindableMouseButton(p.buttons)) { + // This will work if the user clicks while the shortcut change is in progress + p.Use(); + UIView.PopModal(); + var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), + IsControlDown(), IsShiftDown(), + IsAltDown()); + + editingBinding_.value = inputKey; + var uIButton2 = p.source as UIButton; + uIButton2.text = editingBinding_.ToLocalizedString("KEYNAME"); + uIButton2.buttonsMask = UIMouseButton.Left; + editingBinding_ = null; + editingBindingCategory_ = string.Empty; + } + } + + protected void RefreshBindableInputs() { + foreach (var current in component.GetComponentsInChildren()) { + var uITextComponent = current.Find("Binding"); + if (uITextComponent != null) { + var savedInputKey = uITextComponent.objectUserData as SavedInputKey; + if (savedInputKey != null) { + uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + } + } + + var uILabel = current.Find("Name"); + if (uILabel != null) { + uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); + } + } + } + + protected InputKey GetDefaultEntry(string entryName) { + var field = + typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); + if (field == null) { + return 0; + } + + var value = field.GetValue(null); + if (value is InputKey key) { + return key; + } + + return 0; + } + + protected void RefreshKeyMapping() { + foreach (var current in component.GetComponentsInChildren()) { + var uITextComponent = current.Find("Binding"); + var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; + if (editingBinding_ != savedInputKey) { + uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + } + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index c295078b3..4586646d1 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -387,6 +387,24 @@ public static void makeSettings(UIHelperBase helper) { enableLaneConnectorToggle = featureGroup.AddCheckbox(Translation.GetString("Lane_connector"), laneConnectorEnabled, onLaneConnectorEnabledChanged) as UICheckBox; Indent(turnOnRedEnabledToggle); + + // KEYBOARD + ++tabIndex; + + AddOptionTab(tabStrip, Translation.GetString("Keyboard")); + tabStrip.selectedIndex = tabIndex; + + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; + + panelHelper = new UIHelper(currentPanel); + + var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keyboard")); + ((UIPanel)((UIHelper)keyboardGroup).self).gameObject.AddComponent(); #if DEBUG // GLOBAL CONFIG @@ -481,7 +499,7 @@ public static void makeSettings(UIHelperBase helper) { }*/ #endif - tabStrip.selectedIndex = 0; + tabStrip.selectedIndex = 0; } private static void Indent(T component) where T : UIComponent { diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index bac416def..09cc9f43a 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -23,7 +23,7 @@ public class TrafficManagerMod : IUserMod { public string Description => "Manage your city's traffic"; - public void OnEnabled() { + public void OnEnabled() { Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); if (UIView.GetAView() != null) { OnGameIntroLoaded(); diff --git a/TLM/TLM/UI/LinearSpriteButton.cs b/TLM/TLM/UI/LinearSpriteButton.cs index 5ea666009..bc323a9b0 100644 --- a/TLM/TLM/UI/LinearSpriteButton.cs +++ b/TLM/TLM/UI/LinearSpriteButton.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using ColossalFramework; using TrafficManager.State; using TrafficManager.Util; using UnityEngine; @@ -94,8 +95,11 @@ public override void Start() { public abstract string Tooltip { get; } public abstract bool Visible { get; } public abstract void HandleClick(UIMouseEventParameter p); + public virtual SavedInputKey ShortcutKey { + get { return null; } + } - protected override void OnClick(UIMouseEventParameter p) { + protected override void OnClick(UIMouseEventParameter p) { HandleClick(p); UpdateProperties(); } @@ -110,7 +114,11 @@ internal void UpdateProperties() { m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = GetButtonForegroundTextureId(ButtonName, FunctionName, active); m_ForegroundSprites.m_Hovered = m_PressedFgSprite = GetButtonForegroundTextureId(ButtonName, FunctionName, true); - tooltip = Translation.GetString(Tooltip); + var shortcutText = ShortcutKey == null + ? string.Empty + : "\n" + ShortcutKey.ToLocalizedString("KEYNAME"); + tooltip = Translation.GetString(Tooltip) + shortcutText; + isVisible = Visible; this.Invalidate(); } diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs index 8b3a9361d..230e9c624 100644 --- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs @@ -1,35 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class JunctionRestrictionsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.JunctionRestrictions; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.JunctionRestrictions; - } - } - - public override string Tooltip { - get { - return "Junction_restrictions"; - } - } - - public override bool Visible { - get { - return Options.junctionRestrictionsEnabled; - } - } - } -} + public class JunctionRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.JunctionRestrictions; + public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; + public override string Tooltip => "Junction_restrictions"; + public override bool Visible => Options.junctionRestrictionsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyJunctionRestrictionsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs index 17b09ac85..f2f3a3ba0 100644 --- a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs @@ -1,35 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class LaneArrowsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.LaneChange; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.LaneArrows; - } - } - - public override string Tooltip { - get { - return "Change_lane_arrows"; - } - } - - public override bool Visible { - get { - return true; - } - } - } -} + public class LaneArrowsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.LaneChange; + public override ButtonFunction Function => ButtonFunction.LaneArrows; + public override string Tooltip => "Change_lane_arrows"; + public override bool Visible => true; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneArrowTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs index 0394e8b98..cfcd2c9f3 100644 --- a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs @@ -1,35 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class LaneConnectorButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.LaneConnector; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.LaneConnector; - } - } - - public override string Tooltip { - get { - return "Lane_connector"; - } - } - - public override bool Visible { - get { - return Options.laneConnectorEnabled; - } - } - } + public override ToolMode ToolMode => ToolMode.LaneConnector; + public override ButtonFunction Function => ButtonFunction.LaneConnector; + public override string Tooltip => "Lane_connector"; + public override bool Visible => Options.laneConnectorEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneConnectionsTool; + } } diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index ea158ccca..307c8b3da 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using TrafficManager.Manager; using CSUtil.Commons; +using TrafficManager.UI.SubTools; using TrafficManager.Util; namespace TrafficManager.UI.MainMenu { @@ -206,5 +207,37 @@ public void UpdatePosition(Vector2 pos) { absolutePosition = rect.position; Invalidate(); } - } + + public void OnGUI() { + // Some safety checks to not trigger while full screen/modals are open + // Check the key and then click the corresponding button + if (!UIView.HasModalInput() + && !UIView.HasInputFocus()) { + if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { + ClickToolButton(typeof(ToggleTrafficLightsButton)); + } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneArrowsButton)); + } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneConnectorButton)); + } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(PrioritySignsButton)); + } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(JunctionRestrictionsButton)); + } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(SpeedLimitsButton)); + } + } + } + + /// For given button class type, find it in the tool palette and send click + /// Something like typeof(ToggleTrafficLightsButton) + void ClickToolButton(Type t) { + for (var i = 0; i < MENU_BUTTON_TYPES.Length; i++) { + if (MENU_BUTTON_TYPES[i] == t) { + Buttons[i].SimulateClick(); + return; + } + } + } + } } diff --git a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs index e19785e06..10774e837 100644 --- a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs @@ -1,35 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class ManualTrafficLightsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.ManualSwitch; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.ManualTrafficLights; - } - } - - public override string Tooltip { - get { - return "Manual_traffic_lights"; - } - } - - public override bool Visible { - get { - return Options.timedLightsEnabled; - } - } - } + public override ToolMode ToolMode => ToolMode.ManualSwitch; + public override ButtonFunction Function => ButtonFunction.ManualTrafficLights; + public override string Tooltip => "Manual_traffic_lights"; + public override bool Visible => Options.timedLightsEnabled; + } } diff --git a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs index a7d296ba0..2f5b9b3b5 100644 --- a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs +++ b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs @@ -1,35 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class PrioritySignsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.AddPrioritySigns; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.PrioritySigns; - } - } - - public override string Tooltip { - get { - return "Add_priority_signs"; - } - } - - public override bool Visible { - get { - return Options.prioritySignsEnabled; - } - } - } + public override ToolMode ToolMode => ToolMode.AddPrioritySigns; + public override ButtonFunction Function => ButtonFunction.PrioritySigns; + public override string Tooltip => "Add_priority_signs"; + public override bool Visible => Options.prioritySignsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyPrioritySignsTool; + } } diff --git a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs index dd25996b1..0003f5f78 100644 --- a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs +++ b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs @@ -1,35 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class SpeedLimitsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.SpeedLimits; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.SpeedLimits; - } - } - - public override string Tooltip { - get { - return "Speed_limits"; - } - } - - public override bool Visible { - get { - return Options.customSpeedLimitsEnabled; - } - } - } + public override ToolMode ToolMode => ToolMode.SpeedLimits; + public override ButtonFunction Function => ButtonFunction.SpeedLimits; + public override string Tooltip => "Speed_limits"; + public override bool Visible => Options.customSpeedLimitsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeySpeedLimitsTool; + } } diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs index f5c1c1026..53ec43db4 100644 --- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs @@ -1,34 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; +using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class ToggleTrafficLightsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.SwitchTrafficLight; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.ToggleTrafficLights; - } - } - - public override string Tooltip { - get { - return "Switch_traffic_lights"; - } - } - - public override bool Visible { - get { - return true; - } - } - } + public override ToolMode ToolMode => ToolMode.SwitchTrafficLight; + public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; + public override string Tooltip => "Switch_traffic_lights"; + public override bool Visible => true; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyToggleTrafficLightTool; + } } diff --git a/TLM/TLM/UI/ToolMode.cs b/TLM/TLM/UI/ToolMode.cs index efea4c192..8c4de00df 100644 --- a/TLM/TLM/UI/ToolMode.cs +++ b/TLM/TLM/UI/ToolMode.cs @@ -1,21 +1,19 @@ -namespace TrafficManager.UI -{ - public enum ToolMode - { - None = 0, - SwitchTrafficLight = 1, - AddPrioritySigns = 2, - ManualSwitch = 3, - TimedLightsSelectNode = 4, - TimedLightsShowLights = 5, - LaneChange = 6, - TimedLightsAddNode = 7, - TimedLightsRemoveNode = 8, - TimedLightsCopyLights = 9, - SpeedLimits = 10, - VehicleRestrictions = 11, - LaneConnector = 12, - JunctionRestrictions = 13, - ParkingRestrictions = 14 - } -} +namespace TrafficManager.UI { + public enum ToolMode { + None = 0, + SwitchTrafficLight = 1, + AddPrioritySigns = 2, + ManualSwitch = 3, + TimedLightsSelectNode = 4, + TimedLightsShowLights = 5, + LaneChange = 6, + TimedLightsAddNode = 7, + TimedLightsRemoveNode = 8, + TimedLightsCopyLights = 9, + SpeedLimits = 10, + VehicleRestrictions = 11, + LaneConnector = 12, + JunctionRestrictions = 13, + ParkingRestrictions = 14 + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 524b8d9ae..90ac5460d 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -367,7 +367,8 @@ protected override void OnToolGUI(Event e) { activeSubTool.OnToolGUI(e); else base.OnToolGUI(e); - } catch (Exception ex) { + + } catch (Exception ex) { Log.Error("GUI Error: " + ex.ToString()); } } diff --git a/TLM/TLM/UI/UIBase.cs b/TLM/TLM/UI/UIBase.cs index 077930142..b8aee6b54 100644 --- a/TLM/TLM/UI/UIBase.cs +++ b/TLM/TLM/UI/UIBase.cs @@ -94,10 +94,11 @@ public void Show() { Log.Error("Error on Show(): " + e.ToString()); } - foreach (MenuButton button in GetMenu().Buttons) { + foreach (var button in GetMenu().Buttons) { button.UpdateProperties(); } - GetMenu().Show(); + + GetMenu().Show(); Translation.ReloadTutorialTranslations(); TrafficManagerTool.ShowAdvisor("MainMenu"); #if DEBUG diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 642d86a8c..5c953dc31 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -123,5 +123,18 @@ public void UpdatePosition(Vector2 pos) { absolutePosition = rect.position; Invalidate(); } - } + + public void OnGUI() { + if (!UIView.HasModalInput() + && !UIView.HasInputFocus() + && OptionsKeymapping.KeyToggleTMPEMainMenu.IsPressed(Event.current)) + { + LoadingExtension.BaseUI?.ToggleMainMenu(); + } + + // FIXME: Tooltip text is not displayed on the tool button + // var shortcutText = OptionsKeymapping.KeyToggleTMPEMainMenu.ToLocalizedString("KEYNAME"); + // tooltip = Translation.GetString("Keyboard_toggle_TMPE_main_menu") + shortcutText; + } + } } From 7e9fd619b0c0d2bbc6991101318f0a6dc8db8837 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 16 Jun 2019 03:14:24 +0200 Subject: [PATCH 037/142] FIXME: Added Shift+S stay in lane to shortcuts, but it works only if its anything other than Shift+S --- TLM/TLM/Resources/lang.txt | 3 ++- TLM/TLM/State/KeyMapping.cs | 9 +++++++++ TLM/TLM/UI/SubTools/LaneConnectorTool.cs | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 1fc247867..5a1d29970 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -238,4 +238,5 @@ Keyboard_use_lane_arrow_tool Use 'Lane Arrow' Tool Keyboard_use_lane_connections_tool Use 'Lane Connections' Tool Keyboard_use_priority_signs_tool Use 'Priority Signs' Tool Keyboard_use_junction_restrictions_tool Use 'Junction Restrictions' Tool -Keyboard_use_speed_limits_tool Use 'Speed Limits' Tool \ No newline at end of file +Keyboard_use_speed_limits_tool Use 'Speed Limits' Tool +Keyboard_lane_connector_stay_in_lane Lane connector: Stay in lane diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs index 1eb0fc73a..1acecce6e 100644 --- a/TLM/TLM/State/KeyMapping.cs +++ b/TLM/TLM/State/KeyMapping.cs @@ -29,6 +29,9 @@ private void Awake() { KeyJunctionRestrictionsTool); AddKeymapping(Translation.GetString("Keyboard_use_speed_limits_tool"), KeySpeedLimitsTool); + + AddKeymapping(Translation.GetString("Keyboard_lane_connector_stay_in_lane"), + KeyLaneConnectorStayInLane); } } @@ -78,6 +81,12 @@ public class OptionsKeymapping : UICustomControl { SavedInputKey.Encode(KeyCode.S, true, true, false), true); + public static SavedInputKey KeyLaneConnectorStayInLane = + new SavedInputKey("keyLaneConnectorStayInLane", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.S, false, true, false), + true); + private SavedInputKey editingBinding_; private string editingBindingCategory_; diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index 731189844..66366f395 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -188,7 +188,8 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { } bool deleteAll = Input.GetKeyDown(KeyCode.Delete) || Input.GetKeyDown(KeyCode.Backspace); - bool stayInLane = Input.GetKeyDown(KeyCode.S) && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; + bool stayInLane = OptionsKeymapping.KeyLaneConnectorStayInLane.IsPressed(Event.current) + && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; if (stayInLane) deleteAll = true; From 2cb3d37bea2e15c4976c428996d3fe75be3ab26f Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 16 Jun 2019 03:20:41 +0200 Subject: [PATCH 038/142] Added KeyMapping.cs to the C# project --- TLM/TLM/TLM.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index cb126ecb2..ce037a551 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -176,6 +176,7 @@ + From 51206943b505ec4b7ca588ae69bc9e6849ff5113 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 16 Jun 2019 03:24:02 +0200 Subject: [PATCH 039/142] Minor: Downgrade C# syntax version --- TLM/TLM/State/KeyMapping.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs index 1acecce6e..590101fe3 100644 --- a/TLM/TLM/State/KeyMapping.cs +++ b/TLM/TLM/State/KeyMapping.cs @@ -17,17 +17,17 @@ private void Awake() { AddKeymapping(Translation.GetString("Keyboard_toggle_TMPE_main_menu"), KeyToggleTMPEMainMenu); - AddKeymapping(Translation.GetString("Keyboard_toggle_traffic_lights_tool"), + AddKeymapping(Translation.GetString("Keyboard_toggle_traffic_lights_tool"), KeyToggleTrafficLightTool); - AddKeymapping(Translation.GetString("Keyboard_use_lane_arrow_tool"), + AddKeymapping(Translation.GetString("Keyboard_use_lane_arrow_tool"), KeyLaneArrowTool); - AddKeymapping(Translation.GetString("Keyboard_use_lane_connections_tool"), + AddKeymapping(Translation.GetString("Keyboard_use_lane_connections_tool"), KeyLaneConnectionsTool); - AddKeymapping(Translation.GetString("Keyboard_use_priority_signs_tool"), + AddKeymapping(Translation.GetString("Keyboard_use_priority_signs_tool"), KeyPrioritySignsTool); - AddKeymapping(Translation.GetString("Keyboard_use_junction_restrictions_tool"), + AddKeymapping(Translation.GetString("Keyboard_use_junction_restrictions_tool"), KeyJunctionRestrictionsTool); - AddKeymapping(Translation.GetString("Keyboard_use_speed_limits_tool"), + AddKeymapping(Translation.GetString("Keyboard_use_speed_limits_tool"), KeySpeedLimitsTool); AddKeymapping(Translation.GetString("Keyboard_lane_connector_stay_in_lane"), @@ -38,7 +38,7 @@ private void Awake() { public class OptionsKeymapping : UICustomControl { protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; - + public static SavedInputKey KeyToggleTMPEMainMenu = new SavedInputKey("keyToggleTMPEMainMenu", KEYBOARD_SHORTCUTS_FILENAME, @@ -274,8 +274,8 @@ protected InputKey GetDefaultEntry(string entryName) { } var value = field.GetValue(null); - if (value is InputKey key) { - return key; + if (value is InputKey) { + return (InputKey) value; } return 0; From 9ceb18fce4a08e0bd530a21804993542c1fa6f03 Mon Sep 17 00:00:00 2001 From: dymanoid <9433345+dymanoid@users.noreply.github.com> Date: Mon, 17 Jun 2019 00:12:48 +0200 Subject: [PATCH 040/142] Implement changes after review https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/pull/362 --- TLM/TLM/Custom/Data/CustomVehicle.cs | 2 +- TLM/TLM/Custom/Manager/CustomVehicleManager.cs | 2 +- TLM/TLM/Manager/Impl/UtilityManager.cs | 4 ++-- TLM/TLM/Manager/Impl/VehicleStateManager.cs | 4 ++-- TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs | 2 ++ TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs | 3 +++ 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/TLM/TLM/Custom/Data/CustomVehicle.cs b/TLM/TLM/Custom/Data/CustomVehicle.cs index d0de5d7fa..c2048da4c 100644 --- a/TLM/TLM/Custom/Data/CustomVehicle.cs +++ b/TLM/TLM/Custom/Data/CustomVehicle.cs @@ -28,7 +28,7 @@ public static void Spawn(ref Vehicle vehicleData, ushort vehicleId) { while (trailingVehicle != 0) { vehManager.m_vehicles.m_buffer[trailingVehicle].Spawn(trailingVehicle); trailingVehicle = vehManager.m_vehicles.m_buffer[trailingVehicle].m_trailingVehicle; - if (++numIter > vehManager.m_vehicles.m_buffer.Length) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } diff --git a/TLM/TLM/Custom/Manager/CustomVehicleManager.cs b/TLM/TLM/Custom/Manager/CustomVehicleManager.cs index 911e60564..246dd69c8 100644 --- a/TLM/TLM/Custom/Manager/CustomVehicleManager.cs +++ b/TLM/TLM/Custom/Manager/CustomVehicleManager.cs @@ -31,7 +31,7 @@ public bool CustomCreateVehicle(out ushort vehicleId, ref Randomizer r, VehicleI #if BENCHMARK using (var bm = new Benchmark(null, "keep-spare-vehicles")) { #endif - if (this.m_vehicleCount > m_vehicles.m_buffer.Length - 5) { + if (this.m_vehicleCount > Constants.ServiceFactory.VehicleService.MaxVehicleCount - 5) { // prioritize service vehicles and public transport when hitting the vehicle limit ItemClass.Service service = info.GetService(); if (service == ItemClass.Service.Residential || service == ItemClass.Service.Industrial || service == ItemClass.Service.Commercial || service == ItemClass.Service.Office) { diff --git a/TLM/TLM/Manager/Impl/UtilityManager.cs b/TLM/TLM/Manager/Impl/UtilityManager.cs index adfb01fa8..a575eb3fc 100644 --- a/TLM/TLM/Manager/Impl/UtilityManager.cs +++ b/TLM/TLM/Manager/Impl/UtilityManager.cs @@ -123,7 +123,7 @@ public void ResetStuckEntities() { } Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting vehicles that are waiting for a path."); - for (uint vehicleId = 1; vehicleId < VehicleManager.instance.m_vehicles.m_buffer.Length; ++vehicleId) { + for (uint vehicleId = 1; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++vehicleId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing vehicle {vehicleId}."); if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.WaitingPath) != 0) { if (Singleton.instance.m_vehicles.m_buffer[vehicleId].m_path != 0u) { @@ -137,7 +137,7 @@ public void ResetStuckEntities() { } Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting vehicles that are parking and where no parked vehicle is assigned to the driver."); - for (uint vehicleId = 1; vehicleId < VehicleManager.instance.m_vehicles.m_buffer.Length; ++vehicleId) { + for (uint vehicleId = 1; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++vehicleId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing vehicle {vehicleId}."); if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.Parking) != 0) { ushort driverInstanceId = CustomPassengerCarAI.GetDriverInstanceId((ushort)vehicleId, ref Singleton.instance.m_vehicles.m_buffer[vehicleId]); diff --git a/TLM/TLM/Manager/Impl/VehicleStateManager.cs b/TLM/TLM/Manager/Impl/VehicleStateManager.cs index 5c04fb5cf..3058434d8 100644 --- a/TLM/TLM/Manager/Impl/VehicleStateManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleStateManager.cs @@ -33,7 +33,7 @@ protected override void InternalPrintDebugInfo() { } private VehicleStateManager() { - VehicleStates = new VehicleState[VehicleManager.instance.m_vehicles.m_buffer.Length]; + VehicleStates = new VehicleState[Constants.ServiceFactory.VehicleService.MaxVehicleCount]; for (uint i = 0; i < VehicleStates.Length; ++i) { VehicleStates[i] = new VehicleState((ushort)i); } @@ -249,7 +249,7 @@ internal void InitAllVehicles() { Log._Debug("VehicleStateManager: InitAllVehicles()"); VehicleManager vehicleManager = Singleton.instance; - for (uint vehicleId = 0; vehicleId < VehicleManager.instance.m_vehicles.m_buffer.Length; ++vehicleId) { + for (uint vehicleId = 0; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++vehicleId) { Services.VehicleService.ProcessVehicle((ushort)vehicleId, delegate (ushort vId, ref Vehicle vehicle) { if ((vehicle.m_flags & Vehicle.Flags.Created) == 0) { return true; diff --git a/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs b/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs index 5adc27f88..c31229af9 100644 --- a/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs +++ b/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs @@ -11,6 +11,8 @@ private VehicleService() { } + public int MaxVehicleCount => VehicleManager.instance.m_vehicles.m_buffer.Length; + public bool CheckVehicleFlags(ushort vehicleId, Vehicle.Flags flagMask, Vehicle.Flags? expectedResult = default(Vehicle.Flags?)) { bool ret = false; ProcessVehicle(vehicleId, delegate (ushort vId, ref Vehicle vehicle) { diff --git a/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs b/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs index 65a4138a0..9a0129803 100644 --- a/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs +++ b/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs @@ -8,6 +8,9 @@ namespace GenericGameBridge.Service { public delegate bool ParkedVehicleHandler(ushort parkedVehicleId, ref VehicleParked parkedVehicle); public interface IVehicleService { + + int MaxVehicleCount { get; } + bool CheckVehicleFlags(ushort vehicleId, Vehicle.Flags flagMask, Vehicle.Flags? expectedResult = default(Vehicle.Flags?)); bool CheckVehicleFlags2(ushort vehicleId, Vehicle.Flags2 flagMask, Vehicle.Flags2? expectedResult = default(Vehicle.Flags2?)); bool IsVehicleValid(ushort vehicleId); From 7acb367729de618edfc7910c9fe1bdceeed8316b Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 17 Jun 2019 01:01:55 +0200 Subject: [PATCH 041/142] Speed limits MPH/kmph option added, textures added, default speed limits window also --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 50 +++---- TLM/TLM/Resources/12.png | Bin 0 -> 9771 bytes TLM/TLM/Resources/25.png | Bin 0 -> 10290 bytes TLM/TLM/Resources/45.png | Bin 0 -> 9466 bytes TLM/TLM/Resources/5.png | Bin 0 -> 9496 bytes TLM/TLM/Resources/55.png | Bin 0 -> 9649 bytes TLM/TLM/Resources/75.png | Bin 0 -> 10134 bytes TLM/TLM/Resources/lang.txt | 6 +- TLM/TLM/State/ConfigData/Main.cs | 30 +++-- TLM/TLM/State/Options.cs | 15 ++- TLM/TLM/TLM.csproj | 7 + TLM/TLM/TMPE.csproj | 6 + TLM/TLM/Traffic/Data/SpeedLimitDef.cs | 79 +++++++++++ TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 38 +++++- TLM/TLM/UI/TextureResources.cs | 157 ++++++++++++---------- 15 files changed, 274 insertions(+), 114 deletions(-) create mode 100644 TLM/TLM/Resources/12.png create mode 100644 TLM/TLM/Resources/25.png create mode 100644 TLM/TLM/Resources/45.png create mode 100644 TLM/TLM/Resources/5.png create mode 100644 TLM/TLM/Resources/55.png create mode 100644 TLM/TLM/Resources/75.png create mode 100644 TLM/TLM/Traffic/Data/SpeedLimitDef.cs diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index 67f129b3e..73d24e0f6 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -6,6 +6,7 @@ using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; +using TrafficManager.Traffic.Data; using TrafficManager.Util; using UnityEngine; @@ -30,24 +31,24 @@ protected override void InternalPrintDebugInfo() { // TODO implement } - public readonly List AvailableSpeedLimits; + public readonly List AvailableSpeedLimits; private SpeedLimitManager() { - AvailableSpeedLimits = new List(); - AvailableSpeedLimits.Add(10); - AvailableSpeedLimits.Add(20); - AvailableSpeedLimits.Add(30); - AvailableSpeedLimits.Add(40); - AvailableSpeedLimits.Add(50); - AvailableSpeedLimits.Add(60); - AvailableSpeedLimits.Add(70); - AvailableSpeedLimits.Add(80); - AvailableSpeedLimits.Add(90); - AvailableSpeedLimits.Add(100); - AvailableSpeedLimits.Add(110); - AvailableSpeedLimits.Add(120); - AvailableSpeedLimits.Add(130); - AvailableSpeedLimits.Add(0); + AvailableSpeedLimits = new List(); + AvailableSpeedLimits.Add(SpeedLimitDef.Km10); + AvailableSpeedLimits.Add(SpeedLimitDef.Km20); + AvailableSpeedLimits.Add(SpeedLimitDef.Km30); + AvailableSpeedLimits.Add(SpeedLimitDef.Km40); + AvailableSpeedLimits.Add(SpeedLimitDef.Km50); + AvailableSpeedLimits.Add(SpeedLimitDef.Km60); + AvailableSpeedLimits.Add(SpeedLimitDef.Km70); + AvailableSpeedLimits.Add(SpeedLimitDef.Km80); + AvailableSpeedLimits.Add(SpeedLimitDef.Km90); + AvailableSpeedLimits.Add(SpeedLimitDef.Km100); + AvailableSpeedLimits.Add(SpeedLimitDef.Km110); + AvailableSpeedLimits.Add(SpeedLimitDef.Km120); + AvailableSpeedLimits.Add(SpeedLimitDef.Km130); + AvailableSpeedLimits.Add(SpeedLimitDef.NoLimit); vanillaLaneSpeedLimitsByNetInfoName = new Dictionary(); CustomLaneSpeedLimitIndexByNetInfoName = new Dictionary(); @@ -427,7 +428,9 @@ public int GetCustomNetInfoSpeedLimitIndex(NetInfo info) { string infoName = info.name; int speedLimitIndex; if (!CustomLaneSpeedLimitIndexByNetInfoName.TryGetValue(infoName, out speedLimitIndex)) { - return AvailableSpeedLimits.IndexOf(GetVanillaNetInfoSpeedLimit(info, true)); + return SpeedLimitDef.FindKmphInList( + AvailableSpeedLimits, + GetVanillaNetInfoSpeedLimit(info, true)); } return speedLimitIndex; @@ -461,7 +464,7 @@ public void SetCustomNetInfoSpeedLimitIndex(NetInfo info, int customSpeedLimitIn string infoName = info.name; CustomLaneSpeedLimitIndexByNetInfoName[infoName] = customSpeedLimitIndex; - float gameSpeedLimit = ToGameSpeedLimit(AvailableSpeedLimits[customSpeedLimitIndex]); + float gameSpeedLimit = ToGameSpeedLimit(AvailableSpeedLimits[customSpeedLimitIndex].Kmph); // save speed limit in all NetInfos Log._Debug($"Updating parent NetInfo {infoName}: Setting speed limit to {gameSpeedLimit}"); @@ -534,7 +537,7 @@ public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInf if (!MayHaveCustomSpeedLimits(laneInfo)) { return false; } - if (!AvailableSpeedLimits.Contains(speedLimit)) { + if (!SpeedLimitDef.DoesListContainKmph(AvailableSpeedLimits, speedLimit)) { return false; } if (!Services.NetService.IsLaneValid(laneId)) { @@ -556,7 +559,7 @@ public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, ushort s if (!MayHaveCustomSpeedLimits(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId])) { return false; } - if (!AvailableSpeedLimits.Contains(speedLimit)) { + if (!SpeedLimitDef.DoesListContainKmph(AvailableSpeedLimits, speedLimit)) { return false; } @@ -784,7 +787,8 @@ public bool LoadData(List data) { NetInfo info = Singleton.instance.m_segments.m_buffer[segmentId].Info; int customSpeedLimitIndex = GetCustomNetInfoSpeedLimitIndex(info); Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: Custom speed limit index of segment {segmentId} info ({info}, name={info?.name}, lanes={info?.m_lanes} is {customSpeedLimitIndex}"); - if (customSpeedLimitIndex < 0 || AvailableSpeedLimits[customSpeedLimitIndex] != laneSpeedLimit.speedLimit) { + if (customSpeedLimitIndex < 0 + || AvailableSpeedLimits[customSpeedLimitIndex].Kmph != laneSpeedLimit.speedLimit) { // lane speed limit differs from default speed limit Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, laneSpeedLimit.speedLimit); @@ -824,7 +828,7 @@ public bool LoadData(Dictionary data) { continue; ushort customSpeedLimit = LaneToCustomSpeedLimit(e.Value, true); - int customSpeedLimitIndex = AvailableSpeedLimits.IndexOf(customSpeedLimit); + int customSpeedLimitIndex = SpeedLimitDef.FindKmphInList(AvailableSpeedLimits, customSpeedLimit); if (customSpeedLimitIndex >= 0) { SetCustomNetInfoSpeedLimitIndex(netInfo, customSpeedLimitIndex); } @@ -836,7 +840,7 @@ Dictionary ICustomDataManager>.SaveData Dictionary ret = new Dictionary(); foreach (KeyValuePair e in CustomLaneSpeedLimitIndexByNetInfoName) { try { - ushort customSpeedLimit = AvailableSpeedLimits[e.Value]; + ushort customSpeedLimit = AvailableSpeedLimits[e.Value].Kmph; float gameSpeedLimit = ToGameSpeedLimit(customSpeedLimit); ret.Add(e.Key, gameSpeedLimit); diff --git a/TLM/TLM/Resources/12.png b/TLM/TLM/Resources/12.png new file mode 100644 index 0000000000000000000000000000000000000000..2f508a84799615d0eed7f6193eaaade15d42c040 GIT binary patch literal 9771 zcmY+qbx<5n*ewh!?(VWk@Zhcs!Cis{4-h=qCb%r_8Z5XI9D)URm&M&(0t6PfkKg;= z`flAndb*~otLN#SnbW7Ab0Rd<w+RxogT>p%Bjfft|G|M%920#;O&McIVMCFJFa_qxD)Ya#{9>VvhN zzJT4$U9I4p&27JeIlfwffqWd?90Jd3Xoqlc?@|{LR9NPL)p|ocpLvbYiaX z;gif~rOM_d0%LCljRcL@*4|?IQpW@bZ}EyDJT-61y!LsZZF8A;75@mW`%QDPqmb6W z<6n7w4eTNBql&69WI;+SNC8#(U#S+`AB3>*3@&*M1< zlcHB*S7W^AsD#WKxgZDv5;|dN7oYo?k<0V-Le#&V!6* zBqv&BJWT2!16Y`=M8|Ls^kNYXQRg{a?T-^Er*aUL>sBi124`ne7xd@`VH`|GVOURu zUzMo;PiQk6x`2UVUCM#6#dLsMLK3=y=$N6IKsZ4u!tT}RkZgL<9@X)#6NzZg$1s!^GPpwn+s<5&F%uFA+)z=(5U zw~U1udvruCx7@%;8d?aj+%SGD3l2p&`G>l~g0y2(;4?<=tKGVIHo@$E6xpr%Ozh^m0s~3%I z@}3n*EP&(!alMP)BYB@jETl>3`a0R;!$MzGXREeI<=tN~2Y7lT~VMF&RDPfScm1bW%Oj!rMFPQ%jIb?6<6c!Dmq zH~(h)hwBms#RaOwt1f=ulj+-wC{BCA#J(Of*XmKh5CT##ulLw{WTyb?q~r(~RZ!)& z6BTMi!G{_VT#E#5B2TLaRY~`Xw&uu-NMd2PL%_c|!18#)qX41dci?hfnoYhAkMqR! z`nb)21LS^xOLLJdCI(6$0%e&B-?!h}H%*Oh-D$wLR!_)WG+8trz7E7FKmSx1*A{Os zBsd&@cO1`^s>Zncc`7#DKWSGXJ(wt-CLZlA7Ls+=Yl4J#JhF|oX@EKD)uw&o%n2js z?R$dG#6N}IipS{Y$VSRsF61RsTs#s_RS?iF zaE8#+XyU%IT=Y`Ds?Ta#b zt7Rm2=2GKiSt%jE48pXi|iWU%?%T~LLNv%W! z=lXi@KI8OnUTE=(SKujVW8k{DxvA0#%Lia#5v}-Km9Q`*d`Es^=5Ppl;1FxUFCX6o zW^%0^Xbfx>Y&tXCwJdnOBG+@dEf@E2z*} z@IP>OZQ+9rrHla=W=EUQSY5O=)k_ek8TMF+a4kNM{J!0`l!=C1;` AJnuUpl=WA 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 VNl)n?R8SI{h6+%*M$zj1{{apyE|dTO literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/5.png b/TLM/TLM/Resources/5.png new file mode 100644 index 0000000000000000000000000000000000000000..c0d05278367868777bba6a3747e073a3e7ec2e55 GIT binary patch literal 9496 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 HmJ$C0PV7DC literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/55.png b/TLM/TLM/Resources/55.png new file mode 100644 index 0000000000000000000000000000000000000000..656194e74e07fc0d08ea03faef17224b0a5ad6c2 GIT binary patch literal 9649 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

hZlV%-^5#+Yp$O=OvYeuy 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 68534b7ca..88f95f215 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -231,4 +231,8 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Speed_limit_unlimited No limit +Display_speed_limits_mph Display speed limits as mph instead of km/h +Miles_per_hour Miles per hour +Kilometers_per_hour Kilometers per hour diff --git a/TLM/TLM/State/ConfigData/Main.cs b/TLM/TLM/State/ConfigData/Main.cs index ca2ad9d3e..cc0b28cc5 100644 --- a/TLM/TLM/State/ConfigData/Main.cs +++ b/TLM/TLM/State/ConfigData/Main.cs @@ -10,6 +10,7 @@ public class Main { /// Main menu button position /// public int MainMenuButtonX = 464; + public int MainMenuButtonY = 10; public bool MainMenuButtonPosLocked = false; @@ -17,6 +18,7 @@ public class Main { /// Main menu position /// public int MainMenuX = MainMenuPanel.DEFAULT_MENU_X; + public int MainMenuY = MainMenuPanel.DEFAULT_MENU_Y; public bool MainMenuPosLocked = false; @@ -50,20 +52,28 @@ public class Main { /// public bool ShowCompatibilityCheckErrorMessage = false; - ///

- /// Shows warning dialog if any incompatible mods detected - /// - public bool ScanForKnownIncompatibleModsAtStartup = true; + /// + /// Shows warning dialog if any incompatible mods detected + /// + public bool ScanForKnownIncompatibleModsAtStartup = true; - /// - /// Skip disabled mods while running incompatible mod detector - /// - public bool IgnoreDisabledMods = true; + /// + /// Skip disabled mods while running incompatible mod detector + /// + public bool IgnoreDisabledMods = true; + + /// + /// Prefer Miles per hour instead of Kmph (affects speed limits display + /// but internally Kmph are still used). + /// + public bool DisplaySpeedLimitsMph = false; public void AddDisplayedTutorialMessage(string messageKey) { - HashSet newMessages = DisplayedTutorialMessages != null ? new HashSet(DisplayedTutorialMessages) : new HashSet(); + HashSet newMessages = DisplayedTutorialMessages != null + ? new HashSet(DisplayedTutorialMessages) + : new HashSet(); newMessages.Add(messageKey); DisplayedTutorialMessages = newMessages.ToArray(); } } -} +} \ No newline at end of file diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index c295078b3..dd75d4859 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -31,6 +31,7 @@ public class Options : MonoBehaviour { private static UICheckBox showCompatibilityCheckErrorToggle = null; private static UICheckBox scanForKnownIncompatibleModsToggle = null; private static UICheckBox ignoreDisabledModsToggle = null; + private static UICheckBox displayMphToggle = null; private static UICheckBox individualDrivingStyleToggle = null; private static UIDropDown recklessDriversDropdown = null; private static UICheckBox relaxedBussesToggle = null; @@ -234,7 +235,11 @@ public static void makeSettings(UIHelperBase helper) { showCompatibilityCheckErrorToggle = generalGroup.AddCheckbox(Translation.GetString("Notify_me_if_there_is_an_unexpected_mod_conflict"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; - Indent(ignoreDisabledModsToggle); + Indent(ignoreDisabledModsToggle); + displayMphToggle = generalGroup.AddCheckbox( + Translation.GetString("Display_speed_limits_mph"), + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph, + onDisplayMphChanged) as UICheckBox; var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); simAccuracyDropdown = simGroup.AddDropdown(Translation.GetString("Simulation_accuracy") + ":", new string[] { Translation.GetString("Very_high"), Translation.GetString("High"), Translation.GetString("Medium"), Translation.GetString("Low"), Translation.GetString("Very_Low") }, simAccuracy, onSimAccuracyChanged) as UIDropDown; @@ -698,7 +703,13 @@ private static void onIgnoreDisabledModsChanged(bool newValue) { GlobalConfig.WriteConfig(); } - private static void onInstantEffectsChanged(bool newValue) { + private static void onDisplayMphChanged(bool newValue) { + Log._Debug($"Display MPH changed to {newValue}"); + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph = newValue; + GlobalConfig.WriteConfig(); + } + + private static void onInstantEffectsChanged(bool newValue) { if (!checkGameLoaded()) return; diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index cb126ecb2..2a0da6459 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -186,6 +186,7 @@ + @@ -364,6 +365,12 @@ + + + + + + diff --git a/TLM/TLM/TMPE.csproj b/TLM/TLM/TMPE.csproj index 85d2c8c23..8dbd837f2 100644 --- a/TLM/TLM/TMPE.csproj +++ b/TLM/TLM/TMPE.csproj @@ -260,6 +260,12 @@ + + + + + + diff --git a/TLM/TLM/Traffic/Data/SpeedLimitDef.cs b/TLM/TLM/Traffic/Data/SpeedLimitDef.cs new file mode 100644 index 000000000..133f6aad3 --- /dev/null +++ b/TLM/TLM/Traffic/Data/SpeedLimitDef.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using TrafficManager.State; +using TrafficManager.UI; +using UnityEngine; + +namespace TrafficManager.Traffic.Data { + /// + /// Defines a speed limit value with default Kmph and display value of Mph + /// for when the option is set to display Mph. The engine still uses kmph. + /// + public struct SpeedLimitDef { + public readonly ushort Kmph; + public readonly ushort Mph; + + public static SpeedLimitDef Km10 = new SpeedLimitDef(10, 5); + public static SpeedLimitDef Km20 = new SpeedLimitDef(20, 12); + public static SpeedLimitDef Km30 = new SpeedLimitDef(30, 20); + public static SpeedLimitDef Km40 = new SpeedLimitDef(40, 25); + public static SpeedLimitDef Km50 = new SpeedLimitDef(50, 30); + public static SpeedLimitDef Km60 = new SpeedLimitDef(60, 40); + public static SpeedLimitDef Km70 = new SpeedLimitDef(70, 45); + public static SpeedLimitDef Km80 = new SpeedLimitDef(80, 50); + public static SpeedLimitDef Km90 = new SpeedLimitDef(90, 55); + public static SpeedLimitDef Km100 = new SpeedLimitDef(100, 60); + public static SpeedLimitDef Km110 = new SpeedLimitDef(110, 70); + public static SpeedLimitDef Km120 = new SpeedLimitDef(120, 75); + public static SpeedLimitDef Km130 = new SpeedLimitDef(130, 80); + public static SpeedLimitDef NoLimit = new SpeedLimitDef(0, 0); + + public static Dictionary KmphToSpeedLimitDef = + new Dictionary { { 10, Km10 }, { 20, Km20 }, { 30, Km30 }, { 40, Km40 }, { 50, Km50 }, + { 60, Km60 }, { 70, Km70 }, { 80, Km80 }, { 90, Km90 }, { 100, Km100 }, + { 110, Km110 }, { 120, Km120 }, { 130, Km130 }, { 0, NoLimit } }; + + public SpeedLimitDef(ushort kmph, ushort mph) { + Kmph = kmph; + Mph = mph; + } + + public static bool DoesListContainKmph(List lst, ushort kmph) { + foreach (var v in lst) { + if (v.Kmph == kmph) { + return true; + } + } + + return false; + } + + public static int FindKmphInList(List lst, ushort kmph) { + var index = 0; + foreach (var v in lst) { + if (v.Kmph == kmph) { + return index; + } + + index++; + } + + return -1; + } + + public string ToMphString() { + if (Mph == 0) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return Mph + " mph"; + } + + public string ToKmphString() { + if (Kmph == 0) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return Kmph + " km/h"; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 8df46d33a..538f9362d 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -13,6 +13,7 @@ using TrafficManager.Manager.Impl; using TrafficManager.State; using TrafficManager.Traffic; +using TrafficManager.Traffic.Data; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; @@ -259,7 +260,13 @@ private void _guiDefaultsWindow(int num) { GUILayout.FlexibleSpace(); // speed limit sign - GUILayout.Box(TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.AvailableSpeedLimits[currentSpeedLimitIndex]], GUILayout.Width(guiSpeedSignSize), GUILayout.Height(guiSpeedSignSize)); + var limit = SpeedLimitManager.Instance.AvailableSpeedLimits[currentSpeedLimitIndex]; + GUILayout.Box(TextureResources.GetSpeedLimitTexture(limit), + GUILayout.Width(guiSpeedSignSize), + GUILayout.Height(guiSpeedSignSize)); + GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? Translation.GetString("Miles_per_hour") + : Translation.GetString("Kilometers_per_hour")); GUILayout.FlexibleSpace(); GUILayout.EndVertical(); @@ -327,12 +334,23 @@ private void _guiSpeedLimitsWindow(int num) { Color oldColor = GUI.color; for (int i = 0; i < SpeedLimitManager.Instance.AvailableSpeedLimits.Count; ++i) { - if (curSpeedLimitIndex != i) + if (curSpeedLimitIndex != i) { GUI.color = Color.gray; - float signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); - if (GUILayout.Button(TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.AvailableSpeedLimits[i]], GUILayout.Width(signSize), GUILayout.Height(signSize))) { + } + + // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added + GUILayout.BeginVertical(); + var signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); + var limit = SpeedLimitManager.Instance.AvailableSpeedLimits[i]; + if (GUILayout.Button( + TextureResources.GetSpeedLimitTexture(limit), + GUILayout.Width(signSize), + GUILayout.Height(signSize))) { curSpeedLimitIndex = i; } + // For MPH setting display KM/H below, for KM/H setting display MPH + GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? limit.ToKmphString() : limit.ToMphString()); + GUILayout.EndVertical(); GUI.color = oldColor; if (i == 6) { @@ -361,7 +379,7 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo NetManager netManager = Singleton.instance; bool hovered = false; - ushort speedLimitToSet = viewOnly ? (ushort)0 : SpeedLimitManager.Instance.AvailableSpeedLimits[curSpeedLimitIndex]; + ushort speedLimitToSet = viewOnly ? (ushort)0 : SpeedLimitManager.Instance.AvailableSpeedLimits[curSpeedLimitIndex].Kmph; bool showPerLane = showLimitsPerLane; if (!viewOnly) { @@ -411,7 +429,11 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo directions.Add(laneInfo.m_finalDirection); } - bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture(TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId)], camPos, zero, f, xu, yu, x, 0, speedLimitSignSize, !viewOnly); + var limit = SpeedLimitDef.KmphToSpeedLimitDef[ + SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId)]; + bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture( + TextureResources.GetSpeedLimitTexture(limit), + camPos, zero, f, xu, yu, x, 0, speedLimitSignSize, !viewOnly); if (!viewOnly && !onlyMonorailLanes && (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { MainTool.DrawStaticSquareOverlayGridTexture(TextureResources.VehicleInfoSignTextures[ExtVehicleType.PassengerTrain], camPos, zero, f, xu, yu, x, 1, speedLimitSignSize); } @@ -473,7 +495,9 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo } GUI.color = guiColor; - GUI.DrawTexture(boundingBox, TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key)]); + var limit = SpeedLimitDef.KmphToSpeedLimitDef[SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key)]; + GUI.DrawTexture(boundingBox, + TextureResources.GetSpeedLimitTexture(limit)); if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { // change the speed limit to the selected one diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index 60c2d49b0..4716af9a2 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -6,7 +6,9 @@ using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; +using TrafficManager.State; using TrafficManager.Traffic; +using TrafficManager.Traffic.Data; using TrafficManager.UI; using TrafficManager.Util; using UnityEngine; @@ -14,33 +16,32 @@ namespace TrafficManager.UI { - public class TextureResources - { - public static readonly Texture2D RedLightTexture2D; - public static readonly Texture2D YellowRedLightTexture2D; - public static readonly Texture2D YellowLightTexture2D; - public static readonly Texture2D GreenLightTexture2D; - public static readonly Texture2D RedLightStraightTexture2D; - public static readonly Texture2D YellowLightStraightTexture2D; - public static readonly Texture2D GreenLightStraightTexture2D; - public static readonly Texture2D RedLightRightTexture2D; - public static readonly Texture2D YellowLightRightTexture2D; - public static readonly Texture2D GreenLightRightTexture2D; - public static readonly Texture2D RedLightLeftTexture2D; - public static readonly Texture2D YellowLightLeftTexture2D; - public static readonly Texture2D GreenLightLeftTexture2D; - public static readonly Texture2D RedLightForwardRightTexture2D; - public static readonly Texture2D YellowLightForwardRightTexture2D; - public static readonly Texture2D GreenLightForwardRightTexture2D; - public static readonly Texture2D RedLightForwardLeftTexture2D; - public static readonly Texture2D YellowLightForwardLeftTexture2D; - public static readonly Texture2D GreenLightForwardLeftTexture2D; - public static readonly Texture2D PedestrianRedLightTexture2D; - public static readonly Texture2D PedestrianGreenLightTexture2D; - public static readonly Texture2D LightModeTexture2D; - public static readonly Texture2D LightCounterTexture2D; - public static readonly Texture2D PedestrianModeAutomaticTexture2D; - public static readonly Texture2D PedestrianModeManualTexture2D; + public class TextureResources { + public static readonly Texture2D RedLightTexture2D; + public static readonly Texture2D YellowRedLightTexture2D; + public static readonly Texture2D YellowLightTexture2D; + public static readonly Texture2D GreenLightTexture2D; + public static readonly Texture2D RedLightStraightTexture2D; + public static readonly Texture2D YellowLightStraightTexture2D; + public static readonly Texture2D GreenLightStraightTexture2D; + public static readonly Texture2D RedLightRightTexture2D; + public static readonly Texture2D YellowLightRightTexture2D; + public static readonly Texture2D GreenLightRightTexture2D; + public static readonly Texture2D RedLightLeftTexture2D; + public static readonly Texture2D YellowLightLeftTexture2D; + public static readonly Texture2D GreenLightLeftTexture2D; + public static readonly Texture2D RedLightForwardRightTexture2D; + public static readonly Texture2D YellowLightForwardRightTexture2D; + public static readonly Texture2D GreenLightForwardRightTexture2D; + public static readonly Texture2D RedLightForwardLeftTexture2D; + public static readonly Texture2D YellowLightForwardLeftTexture2D; + public static readonly Texture2D GreenLightForwardLeftTexture2D; + public static readonly Texture2D PedestrianRedLightTexture2D; + public static readonly Texture2D PedestrianGreenLightTexture2D; + public static readonly Texture2D LightModeTexture2D; + public static readonly Texture2D LightCounterTexture2D; + public static readonly Texture2D PedestrianModeAutomaticTexture2D; + public static readonly Texture2D PedestrianModeManualTexture2D; public static readonly IDictionary PrioritySignTextures; public static readonly Texture2D SignRemoveTexture2D; public static readonly Texture2D ClockPlayTexture2D; @@ -54,10 +55,10 @@ public class TextureResources public static readonly Texture2D LaneChangeAllowedTexture2D; public static readonly Texture2D UturnAllowedTexture2D; public static readonly Texture2D UturnForbiddenTexture2D; - public static readonly Texture2D RightOnRedForbiddenTexture2D; - public static readonly Texture2D RightOnRedAllowedTexture2D; - public static readonly Texture2D LeftOnRedForbiddenTexture2D; - public static readonly Texture2D LeftOnRedAllowedTexture2D; + public static readonly Texture2D RightOnRedForbiddenTexture2D; + public static readonly Texture2D RightOnRedAllowedTexture2D; + public static readonly Texture2D LeftOnRedForbiddenTexture2D; + public static readonly Texture2D LeftOnRedAllowedTexture2D; public static readonly Texture2D EnterBlockedJunctionAllowedTexture2D; public static readonly Texture2D EnterBlockedJunctionForbiddenTexture2D; public static readonly Texture2D PedestrianCrossingAllowedTexture2D; @@ -83,39 +84,39 @@ static TextureResources() // simple RedLightTexture2D = LoadDllResource("light_1_1.png", 103, 243); - YellowRedLightTexture2D = LoadDllResource("light_1_2.png", 103, 243); - GreenLightTexture2D = LoadDllResource("light_1_3.png", 103, 243); - // forward - RedLightStraightTexture2D = LoadDllResource("light_2_1.png", 103, 243); - YellowLightStraightTexture2D = LoadDllResource("light_2_2.png", 103, 243); - GreenLightStraightTexture2D = LoadDllResource("light_2_3.png", 103, 243); - // right - RedLightRightTexture2D = LoadDllResource("light_3_1.png", 103, 243); - YellowLightRightTexture2D = LoadDllResource("light_3_2.png", 103, 243); - GreenLightRightTexture2D = LoadDllResource("light_3_3.png", 103, 243); - // left - RedLightLeftTexture2D = LoadDllResource("light_4_1.png", 103, 243); - YellowLightLeftTexture2D = LoadDllResource("light_4_2.png", 103, 243); - GreenLightLeftTexture2D = LoadDllResource("light_4_3.png", 103, 243); - // forwardright - RedLightForwardRightTexture2D = LoadDllResource("light_5_1.png", 103, 243); - YellowLightForwardRightTexture2D = LoadDllResource("light_5_2.png", 103, 243); - GreenLightForwardRightTexture2D = LoadDllResource("light_5_3.png", 103, 243); - // forwardleft - RedLightForwardLeftTexture2D = LoadDllResource("light_6_1.png", 103, 243); - YellowLightForwardLeftTexture2D = LoadDllResource("light_6_2.png", 103, 243); - GreenLightForwardLeftTexture2D = LoadDllResource("light_6_3.png", 103, 243); - // yellow - YellowLightTexture2D = LoadDllResource("light_yellow.png", 103, 243); - // pedestrian - PedestrianRedLightTexture2D = LoadDllResource("pedestrian_light_1.png", 73, 123); - PedestrianGreenLightTexture2D = LoadDllResource("pedestrian_light_2.png", 73, 123); - // light mode - LightModeTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_mode.png"), 103, 95); - LightCounterTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_counter.png"), 103, 95); - // pedestrian mode - PedestrianModeAutomaticTexture2D = LoadDllResource("pedestrian_mode_1.png", 73, 70); - PedestrianModeManualTexture2D = LoadDllResource("pedestrian_mode_2.png", 73, 73); + YellowRedLightTexture2D = LoadDllResource("light_1_2.png", 103, 243); + GreenLightTexture2D = LoadDllResource("light_1_3.png", 103, 243); + // forward + RedLightStraightTexture2D = LoadDllResource("light_2_1.png", 103, 243); + YellowLightStraightTexture2D = LoadDllResource("light_2_2.png", 103, 243); + GreenLightStraightTexture2D = LoadDllResource("light_2_3.png", 103, 243); + // right + RedLightRightTexture2D = LoadDllResource("light_3_1.png", 103, 243); + YellowLightRightTexture2D = LoadDllResource("light_3_2.png", 103, 243); + GreenLightRightTexture2D = LoadDllResource("light_3_3.png", 103, 243); + // left + RedLightLeftTexture2D = LoadDllResource("light_4_1.png", 103, 243); + YellowLightLeftTexture2D = LoadDllResource("light_4_2.png", 103, 243); + GreenLightLeftTexture2D = LoadDllResource("light_4_3.png", 103, 243); + // forwardright + RedLightForwardRightTexture2D = LoadDllResource("light_5_1.png", 103, 243); + YellowLightForwardRightTexture2D = LoadDllResource("light_5_2.png", 103, 243); + GreenLightForwardRightTexture2D = LoadDllResource("light_5_3.png", 103, 243); + // forwardleft + RedLightForwardLeftTexture2D = LoadDllResource("light_6_1.png", 103, 243); + YellowLightForwardLeftTexture2D = LoadDllResource("light_6_2.png", 103, 243); + GreenLightForwardLeftTexture2D = LoadDllResource("light_6_3.png", 103, 243); + // yellow + YellowLightTexture2D = LoadDllResource("light_yellow.png", 103, 243); + // pedestrian + PedestrianRedLightTexture2D = LoadDllResource("pedestrian_light_1.png", 73, 123); + PedestrianGreenLightTexture2D = LoadDllResource("pedestrian_light_2.png", 73, 123); + // light mode + LightModeTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_mode.png"), 103, 95); + LightCounterTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_counter.png"), 103, 95); + // pedestrian mode + PedestrianModeAutomaticTexture2D = LoadDllResource("pedestrian_mode_1.png", 73, 70); + PedestrianModeManualTexture2D = LoadDllResource("pedestrian_mode_2.png", 73, 73); // priority signs PrioritySignTextures = new TinyDictionary(); @@ -133,8 +134,18 @@ static TextureResources() ClockTestTexture2D = LoadDllResource("clock_test.png", 512, 512); SpeedLimitTextures = new TinyDictionary(); - foreach (ushort speedLimit in SpeedLimitManager.Instance.AvailableSpeedLimits) { - SpeedLimitTextures.Add(speedLimit, LoadDllResource(speedLimit.ToString() + ".png", 200, 200)); + // Load speed limit signs for Kmph and Mph + foreach (var speedLimit in SpeedLimitManager.Instance.AvailableSpeedLimits) { + if (!SpeedLimitTextures.ContainsKey(speedLimit.Kmph)) { + SpeedLimitTextures.Add(speedLimit.Kmph, + LoadDllResource(speedLimit.Kmph + ".png", 200, 200)); + } + + if (!SpeedLimitTextures.ContainsKey(speedLimit.Mph)) { + var resource = LoadDllResource(speedLimit.Mph + ".png", 200, 200); + SpeedLimitTextures.Add(speedLimit.Mph, + resource != null ? resource : SpeedLimitTextures[10]); + } } VehicleRestrictionTextures = new TinyDictionary>(); @@ -164,12 +175,12 @@ static TextureResources() UturnAllowedTexture2D = LoadDllResource("uturn_allowed.png", 200, 200); UturnForbiddenTexture2D = LoadDllResource("uturn_forbidden.png", 200, 200); - RightOnRedAllowedTexture2D = LoadDllResource("right_on_red_allowed.png", 200, 200); - RightOnRedForbiddenTexture2D = LoadDllResource("right_on_red_forbidden.png", 200, 200); - LeftOnRedAllowedTexture2D = LoadDllResource("left_on_red_allowed.png", 200, 200); - LeftOnRedForbiddenTexture2D = LoadDllResource("left_on_red_forbidden.png", 200, 200); + RightOnRedAllowedTexture2D = LoadDllResource("right_on_red_allowed.png", 200, 200); + RightOnRedForbiddenTexture2D = LoadDllResource("right_on_red_forbidden.png", 200, 200); + LeftOnRedAllowedTexture2D = LoadDllResource("left_on_red_allowed.png", 200, 200); + LeftOnRedForbiddenTexture2D = LoadDllResource("left_on_red_forbidden.png", 200, 200); - EnterBlockedJunctionAllowedTexture2D = LoadDllResource("enterblocked_allowed.png", 200, 200); + EnterBlockedJunctionAllowedTexture2D = LoadDllResource("enterblocked_allowed.png", 200, 200); EnterBlockedJunctionForbiddenTexture2D = LoadDllResource("enterblocked_forbidden.png", 200, 200); PedestrianCrossingAllowedTexture2D = LoadDllResource("crossing_allowed.png", 200, 200); @@ -193,6 +204,10 @@ static TextureResources() WindowBackgroundTexture2D = LoadDllResource("WindowBackground.png", 16, 60); } + public static Texture2D GetSpeedLimitTexture(SpeedLimitDef limit) { + return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? SpeedLimitTextures[limit.Mph] : SpeedLimitTextures[limit.Kmph]; + } + private static Texture2D LoadDllResource(string resourceName, int width, int height) { #if DEBUG From 7156c43abbbd7894158871867cb356bc5fd2c52f Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 17 Jun 2019 01:05:09 +0200 Subject: [PATCH 042/142] Editorconfig file instructs the IDE to use 8 space indentation and tabify lines. Can also contain formatting instructions to be added later --- TLM/.editorconfig | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 TLM/.editorconfig diff --git a/TLM/.editorconfig b/TLM/.editorconfig new file mode 100644 index 000000000..f9530af21 --- /dev/null +++ b/TLM/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.{cs,vb}] +indent_style = tab +indent_size = 8 +trim_trailing_whitespace = true + +[*.cs] +# csharp_new_line_before_open_brace = methods From 88f310305a12c8bbeba93caedef7c59b42a8e198 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 17 Jun 2019 01:09:21 +0200 Subject: [PATCH 043/142] Review notes on translations --- TLM/TLM/Resources/lang.txt | 18 +++++++++--------- TLM/TLM/State/KeyMapping.cs | 16 ++++++++-------- TLM/TLM/UI/UIMainMenuButton.cs | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 5a1d29970..a6b1770c3 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -59,7 +59,7 @@ All_vehicles_may_ignore_lane_arrows All vehicles may ignore lane arrows Busses_may_ignore_lane_arrows Buses may ignore lane arrows Reckless_driving Reckless driving TMPE_Title Traffic Manager: President Edition -Settings_are_defined_for_each_savegame_separately Settings are defined for each savegame separately. Please open settings while in-game. +Settings_are_defined_for_each_savegame_separately Settings can only be changed whilst in-game, and are unique to each save game. Simulation_accuracy Simulation accuracy (higher accuracy reduces performance) Enable_highway_specific_lane_merging/splitting_rules Enable highway specific lane merging/splitting rules Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Drivers like to change their lane (only applied if Advanced AI is enabled) @@ -232,11 +232,11 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict -Keyboard_toggle_TMPE_main_menu Toggle TM:PE Main Menu -Keyboard_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool -Keyboard_use_lane_arrow_tool Use 'Lane Arrow' Tool -Keyboard_use_lane_connections_tool Use 'Lane Connections' Tool -Keyboard_use_priority_signs_tool Use 'Priority Signs' Tool -Keyboard_use_junction_restrictions_tool Use 'Junction Restrictions' Tool -Keyboard_use_speed_limits_tool Use 'Speed Limits' Tool -Keyboard_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs index 590101fe3..3cd1bc725 100644 --- a/TLM/TLM/State/KeyMapping.cs +++ b/TLM/TLM/State/KeyMapping.cs @@ -14,23 +14,23 @@ namespace TrafficManager.State { public class OptionsKeymappingMain : OptionsKeymapping { private void Awake() { TryCreateConfig(); - AddKeymapping(Translation.GetString("Keyboard_toggle_TMPE_main_menu"), + AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), KeyToggleTMPEMainMenu); - AddKeymapping(Translation.GetString("Keyboard_toggle_traffic_lights_tool"), + AddKeymapping(Translation.GetString("Keybind_toggle_traffic_lights_tool"), KeyToggleTrafficLightTool); - AddKeymapping(Translation.GetString("Keyboard_use_lane_arrow_tool"), + AddKeymapping(Translation.GetString("Keybind_use_lane_arrow_tool"), KeyLaneArrowTool); - AddKeymapping(Translation.GetString("Keyboard_use_lane_connections_tool"), + AddKeymapping(Translation.GetString("Keybind_use_lane_connections_tool"), KeyLaneConnectionsTool); - AddKeymapping(Translation.GetString("Keyboard_use_priority_signs_tool"), + AddKeymapping(Translation.GetString("Keybind_use_priority_signs_tool"), KeyPrioritySignsTool); - AddKeymapping(Translation.GetString("Keyboard_use_junction_restrictions_tool"), + AddKeymapping(Translation.GetString("Keybind_use_junction_restrictions_tool"), KeyJunctionRestrictionsTool); - AddKeymapping(Translation.GetString("Keyboard_use_speed_limits_tool"), + AddKeymapping(Translation.GetString("Keybind_use_speed_limits_tool"), KeySpeedLimitsTool); - AddKeymapping(Translation.GetString("Keyboard_lane_connector_stay_in_lane"), + AddKeymapping(Translation.GetString("Keybind_lane_connector_stay_in_lane"), KeyLaneConnectorStayInLane); } } diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 5c953dc31..4d5f3bf00 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -134,7 +134,7 @@ public void OnGUI() { // FIXME: Tooltip text is not displayed on the tool button // var shortcutText = OptionsKeymapping.KeyToggleTMPEMainMenu.ToLocalizedString("KEYNAME"); - // tooltip = Translation.GetString("Keyboard_toggle_TMPE_main_menu") + shortcutText; + // tooltip = Translation.GetString("Keybind_toggle_TMPE_main_menu") + shortcutText; } } } From 3874b3d53792d6430ead739ebef347af0d064d09 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 17 Jun 2019 03:28:22 +0200 Subject: [PATCH 044/142] Fixed keyboard to become keybinds; Added DE/RU translations --- TLM/TLM/Resources/lang.txt | 1 + TLM/TLM/Resources/lang_de.txt | 11 ++++++++++- TLM/TLM/Resources/lang_ru.txt | 11 ++++++++++- TLM/TLM/State/Options.cs | 4 ++-- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index a6b1770c3..4851e3384 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -240,3 +240,4 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index 63b2c749a..5c5b70404 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Gilt auch für Links- & R Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde +Keybind_toggle_TMPE_main_menu TM:PE-Hauptmenü umschalten +Keybind_toggle_traffic_lights_tool Werkzeug 'Ampeln setzen' verwenden +Keybind_use_lane_arrow_tool Werkzeug 'Richtungspfeile' verwenden +Keybind_use_lane_connections_tool Werkzeug 'Fahrspurverbinder' verwenden +Keybind_use_priority_signs_tool Werkzeug 'Vorfahrtsschilder' verwenden +Keybind_use_junction_restrictions_tool Werkzeug 'Kreuzungsbeschränkungen' verwenden +Keybind_use_speed_limits_tool Werkzeug 'Geschwindigkeitsbeschränkungen' verwenden +Keybind_lane_connector_stay_in_lane Fahrspurverbinder: Bleib auf der Fahrspur +Keybinds Tastenkombinationen diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 4193e0a8c..b9ec72983 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Разрешить нап Scan_for_known_incompatible_mods_on_startup Сканирование известных несовместимых модов при запуске Ignore_disabled_mods Игнорировать отключённые моды Traffic_Manager_detected_incompatible_mods Traffic Manager обнаружил несовместимые моды -Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов +Keybind_toggle_TMPE_main_menu Показать или скрыть меню TM:PE +Keybind_toggle_traffic_lights_tool Инструмент 'Установка светофора' +Keybind_use_lane_arrow_tool Инструмент 'Стрелки движения' +Keybind_use_lane_connections_tool Инструмент 'Линии движения' +Keybind_use_priority_signs_tool Инструмент 'Знаки приоритета' +Keybind_use_junction_restrictions_tool Инструмент 'Junction Restrictions' +Keybind_use_speed_limits_tool Инструмент 'Ограничения на перекрёстках' +Keybind_lane_connector_stay_in_lane Линии движения: Оставаться в своей полосе +Keybinds Горячие клавиши diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index 4586646d1..941c7183f 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -391,7 +391,7 @@ public static void makeSettings(UIHelperBase helper) { // KEYBOARD ++tabIndex; - AddOptionTab(tabStrip, Translation.GetString("Keyboard")); + AddOptionTab(tabStrip, Translation.GetString("Keybinds")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; @@ -403,7 +403,7 @@ public static void makeSettings(UIHelperBase helper) { panelHelper = new UIHelper(currentPanel); - var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keyboard")); + var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); ((UIPanel)((UIHelper)keyboardGroup).self).gameObject.AddComponent(); #if DEBUG From 1e10393a87ce8cb30d25fa11a97ea35aff1f89bc Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 17 Jun 2019 03:31:59 +0200 Subject: [PATCH 045/142] Added missing languages as copy of english --- TLM/TLM/Resources/lang_es.txt | 11 ++++++++++- TLM/TLM/Resources/lang_fr.txt | 9 +++++++++ TLM/TLM/Resources/lang_it.txt | 11 ++++++++++- TLM/TLM/Resources/lang_ja.txt | 11 ++++++++++- TLM/TLM/Resources/lang_ko.txt | 9 +++++++++ TLM/TLM/Resources/lang_nl.txt | 11 ++++++++++- TLM/TLM/Resources/lang_pl.txt | 11 ++++++++++- TLM/TLM/Resources/lang_pt.txt | 11 ++++++++++- TLM/TLM/Resources/lang_zh-tw.txt | 11 ++++++++++- TLM/TLM/Resources/lang_zh.txt | 11 ++++++++++- 10 files changed, 98 insertions(+), 8 deletions(-) diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 21457ec8a..ef32ab98e 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index b40634d4b..ff73e83f0 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -232,3 +232,12 @@ Scan_for_known_incompatible_mods_on_startup Rechercher les mods incompatibles au Ignore_disabled_mods Ignorer les mods désactivés Traffic_Manager_detected_incompatible_mods Le gestionnaire de trafic a détecté des mods incompatibles Notify_me_if_there_is_an_unexpected_mod_conflict Afficher un message d'erreur si un mod incompatible est détecté +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index a4f86a030..13a66e9dc 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index fdbd694c5..8a9de1b62 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets 一方通行路の左右 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index 1bc09409f..c070f2d83 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -232,3 +232,12 @@ Scan_for_known_incompatible_mods_on_startup 게임 실행시 비호환되는 모 Ignore_disabled_mods 비활성화된 모드 무시하기 Traffic_Manager_detected_incompatible_mods Traffic Manager와 비호환되는 모드 감지 Notify_me_if_there_is_an_unexpected_mod_conflict 모드와 비 호환되는 모드 발견 시 에러 보여주기 +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 5f748af47..4f610b89f 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 9200d8b58..219780204 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Uwzględnia również skr Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie gry Ignore_disabled_mods Ignoruj wyłączone mody Traffic_Manager_detected_incompatible_mods Traffic Manager wykrył niekompatybilne mody -Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index 1993850e4..df1f1abad 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index ff86907fa..f8bb47bf1 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index a65dc8ff7..48cfe4c2b 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -231,4 +231,13 @@ Also_apply_to_left/right_turns_between_one-way_streets 单行道左右转也适 Scan_for_known_incompatible_mods_on_startup 启动时检测是否存在已知不兼容MOD Ignore_disabled_mods 忽略未启用MOD Traffic_Manager_detected_incompatible_mods 检测到的不兼容MOD -Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybinds Keybinds From d68efff147db6802173f08e1c4afb8d4195b7a36 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 17 Jun 2019 03:33:36 +0200 Subject: [PATCH 046/142] Missed/misplaced one line in RU translation --- TLM/TLM/Resources/lang_ru.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index b9ec72983..bbe68a1b4 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -237,7 +237,7 @@ Keybind_toggle_traffic_lights_tool Инструмент 'Установка св Keybind_use_lane_arrow_tool Инструмент 'Стрелки движения' Keybind_use_lane_connections_tool Инструмент 'Линии движения' Keybind_use_priority_signs_tool Инструмент 'Знаки приоритета' -Keybind_use_junction_restrictions_tool Инструмент 'Junction Restrictions' -Keybind_use_speed_limits_tool Инструмент 'Ограничения на перекрёстках' +Keybind_use_junction_restrictions_tool Инструмент 'Ограничения на перекрёстках' +Keybind_use_speed_limits_tool Инструмент 'Ограничения скорости' Keybind_lane_connector_stay_in_lane Линии движения: Оставаться в своей полосе Keybinds Горячие клавиши From 1f671ed2d0025b7f3a0be551d4e8c5d5c0dcf4bc Mon Sep 17 00:00:00 2001 From: Dmytro Lytovchenko Date: Mon, 17 Jun 2019 10:27:50 +0200 Subject: [PATCH 047/142] Update lang_pl.txt Adding PL translations --- TLM/TLM/Resources/lang_pl.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 219780204..4f740c718 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -232,12 +232,12 @@ Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie Ignore_disabled_mods Ignoruj wyłączone mody Traffic_Manager_detected_incompatible_mods Traffic Manager wykrył niekompatybilne mody Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów -Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu -Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool -Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool -Keybind_use_lane_connections_tool Use 'Lane Connections' Tool -Keybind_use_priority_signs_tool Use 'Priority Signs' Tool -Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool -Keybind_use_speed_limits_tool Use 'Speed Limits' Tool -Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane -Keybinds Keybinds +Keybind_toggle_TMPE_main_menu Menu TM:PE +Keybind_toggle_traffic_lights_tool Narzędzie 'Przełącznik Sygnalizacji świetlnej' +Keybind_use_lane_arrow_tool Narzędzie 'Strzałki pasów ruchu' +Keybind_use_lane_connections_tool Narzędzie 'Połącz pasy ruchu' +Keybind_use_priority_signs_tool Narzędzie 'Znaki pierwszeństwa' +Keybind_use_junction_restrictions_tool Narzędzie 'Ograniczenia na skrzyżowaniu' +Keybind_use_speed_limits_tool Narzędzie 'Limity prędkości' +Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie +Keybinds Skróty klawiszowe From 7899408bc8ac41d3221665843762ee7f1c2e16f1 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 17 Jun 2019 22:24:23 +0200 Subject: [PATCH 048/142] Removed speed index everywhere and using floats now; New textures 5..140 with step 5. --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 267 ++++++++---------- .../Manager/Impl/VehicleBehaviorManager.cs | 12 +- TLM/TLM/Resources/105.png | Bin 0 -> 12968 bytes TLM/TLM/Resources/110.png | Bin 82179 -> 11112 bytes TLM/TLM/Resources/115.png | Bin 0 -> 11128 bytes TLM/TLM/Resources/12.png | Bin 9771 -> 0 bytes TLM/TLM/Resources/125.png | Bin 0 -> 14232 bytes TLM/TLM/Resources/135.png | Bin 0 -> 14481 bytes TLM/TLM/Resources/140.png | Bin 0 -> 13824 bytes TLM/TLM/Resources/15.png | Bin 0 -> 12960 bytes TLM/TLM/Resources/35.png | Bin 0 -> 14158 bytes TLM/TLM/Resources/5.png | Bin 9496 -> 9491 bytes TLM/TLM/Resources/65.png | Bin 0 -> 14154 bytes TLM/TLM/Resources/85.png | Bin 0 -> 14368 bytes TLM/TLM/Resources/95.png | Bin 0 -> 14107 bytes TLM/TLM/State/Configuration.cs | 4 +- TLM/TLM/State/Flags.cs | 74 ++--- TLM/TLM/TLM.csproj | 17 +- TLM/TLM/Traffic/Data/SpeedLimit.cs | 157 ++++++++++ TLM/TLM/Traffic/Data/SpeedLimitDef.cs | 79 ------ TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 199 ++++++++----- TLM/TLM/UI/TextureResources.cs | 90 +++--- TLM/TLM/UI/TrafficManagerTool.cs | 17 +- 23 files changed, 542 insertions(+), 374 deletions(-) create mode 100644 TLM/TLM/Resources/105.png create mode 100644 TLM/TLM/Resources/115.png delete mode 100644 TLM/TLM/Resources/12.png create mode 100644 TLM/TLM/Resources/125.png create mode 100644 TLM/TLM/Resources/135.png create mode 100644 TLM/TLM/Resources/140.png create mode 100644 TLM/TLM/Resources/15.png create mode 100644 TLM/TLM/Resources/35.png create mode 100644 TLM/TLM/Resources/65.png create mode 100644 TLM/TLM/Resources/85.png create mode 100644 TLM/TLM/Resources/95.png create mode 100644 TLM/TLM/Traffic/Data/SpeedLimit.cs delete mode 100644 TLM/TLM/Traffic/Data/SpeedLimitDef.cs diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index 73d24e0f6..b3b34c7f2 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -15,48 +15,32 @@ public class SpeedLimitManager : AbstractGeometryObservingManager, ICustomDataMa public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Monorail; + /// Ingame speed units, max possible speed public const float MAX_SPEED = 10f * 2f; // 1000 km/h + private Dictionary vanillaLaneSpeedLimitsByNetInfoName; // For each NetInfo (by name) and lane index: game default speed limit private Dictionary> childNetInfoNamesByCustomizableNetInfoName; // For each NetInfo (by name): Parent NetInfo (name) private List customizableNetInfos; - internal Dictionary CustomLaneSpeedLimitIndexByNetInfoName; // For each NetInfo (by name) and lane index: custom speed limit index + internal Dictionary CustomLaneSpeedLimitByNetInfoName; // For each NetInfo (by name) and lane index: custom speed limit internal Dictionary NetInfoByName; // For each name: NetInfo public static readonly SpeedLimitManager Instance = new SpeedLimitManager(); - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"- Not implemented -"); - // TODO implement - } - - public readonly List AvailableSpeedLimits; - private SpeedLimitManager() { - AvailableSpeedLimits = new List(); - AvailableSpeedLimits.Add(SpeedLimitDef.Km10); - AvailableSpeedLimits.Add(SpeedLimitDef.Km20); - AvailableSpeedLimits.Add(SpeedLimitDef.Km30); - AvailableSpeedLimits.Add(SpeedLimitDef.Km40); - AvailableSpeedLimits.Add(SpeedLimitDef.Km50); - AvailableSpeedLimits.Add(SpeedLimitDef.Km60); - AvailableSpeedLimits.Add(SpeedLimitDef.Km70); - AvailableSpeedLimits.Add(SpeedLimitDef.Km80); - AvailableSpeedLimits.Add(SpeedLimitDef.Km90); - AvailableSpeedLimits.Add(SpeedLimitDef.Km100); - AvailableSpeedLimits.Add(SpeedLimitDef.Km110); - AvailableSpeedLimits.Add(SpeedLimitDef.Km120); - AvailableSpeedLimits.Add(SpeedLimitDef.Km130); - AvailableSpeedLimits.Add(SpeedLimitDef.NoLimit); - vanillaLaneSpeedLimitsByNetInfoName = new Dictionary(); - CustomLaneSpeedLimitIndexByNetInfoName = new Dictionary(); + CustomLaneSpeedLimitByNetInfoName = new Dictionary(); customizableNetInfos = new List(); childNetInfoNamesByCustomizableNetInfoName = new Dictionary>(); NetInfoByName = new Dictionary(); } + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"- Not implemented -"); + // TODO implement + } + /// /// Determines if custom speed limits may be assigned to the given segment. /// @@ -87,42 +71,47 @@ public bool MayHaveCustomSpeedLimits(NetInfo.Lane laneInfo) { /// /// /// - /// - public ushort GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) { + /// Mean speed limit, average for custom and default lane speeds + public float GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) { // calculate the currently set mean speed limit if (segmentId == 0 || (Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) { - return 0; + return 0.0f; } var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - int laneIndex = 0; - float meanSpeedLimit = 0f; + var curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + var laneIndex = 0; + var meanSpeedLimit = 0f; uint validLanes = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - NetInfo.Direction d = laneInfo.m_finalDirection; - if (d != finalDir) + var laneInfo = segmentInfo.m_lanes[laneIndex]; + var d = laneInfo.m_finalDirection; + if (d != finalDir) { goto nextIter; - if (!MayHaveCustomSpeedLimits(laneInfo)) + } + if (!MayHaveCustomSpeedLimits(laneInfo)) { goto nextIter; + } - ushort? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); - if (setSpeedLimit != null) - meanSpeedLimit += ToGameSpeedLimit((ushort)setSpeedLimit); // custom speed limit - else + var setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); + if (setSpeedLimit != null) { + meanSpeedLimit += ToGameSpeedLimit(setSpeedLimit.Value); // custom speed limit + } else { meanSpeedLimit += laneInfo.m_speedLimit; // game default + } + ++validLanes; - nextIter: + nextIter: curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; laneIndex++; } - if (validLanes > 0) - meanSpeedLimit /= (float)validLanes; - ushort ret = LaneToCustomSpeedLimit(meanSpeedLimit); - return ret; + if (validLanes > 0) { + meanSpeedLimit /= validLanes; + } + + return meanSpeedLimit; } /// @@ -132,25 +121,26 @@ public ushort GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) /// /// /// - public ushort GetAverageDefaultCustomSpeedLimit(NetInfo segmentInfo, NetInfo.Direction? finalDir=null) { - float meanSpeedLimit = 0f; + public float GetAverageDefaultCustomSpeedLimit(NetInfo segmentInfo, NetInfo.Direction? finalDir=null) { + var meanSpeedLimit = 0f; uint validLanes = 0; - for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[i]; - NetInfo.Direction d = laneInfo.m_finalDirection; - if (finalDir != null && d != finalDir) + foreach (var laneInfo in segmentInfo.m_lanes) { + var d = laneInfo.m_finalDirection; + if (finalDir != null && d != finalDir) { continue; - if (!MayHaveCustomSpeedLimits(laneInfo)) + } + if (!MayHaveCustomSpeedLimits(laneInfo)) { continue; - + } + meanSpeedLimit += laneInfo.m_speedLimit; ++validLanes; } - if (validLanes > 0) - meanSpeedLimit /= (float)validLanes; - ushort ret = LaneToCustomSpeedLimit(meanSpeedLimit); - return ret; + if (validLanes > 0) { + meanSpeedLimit /= validLanes; + } + return meanSpeedLimit; } /// @@ -189,11 +179,11 @@ public ushort GetAverageCustomSpeedLimit(ushort segmentId, ref NetSegment segmen /// /// /// - public ushort GetCustomSpeedLimit(uint laneId) { + public float GetCustomSpeedLimit(uint laneId) { // check custom speed limit - ushort? setSpeedLimit = Flags.getLaneSpeedLimit(laneId); + var setSpeedLimit = Flags.getLaneSpeedLimit(laneId); if (setSpeedLimit != null) { - return (ushort)setSpeedLimit; + return setSpeedLimit.Value; } // check default speed limit @@ -212,8 +202,7 @@ public ushort GetCustomSpeedLimit(uint laneId) { if (!MayHaveCustomSpeedLimits(laneInfo)) return 0; - ushort ret = LaneToCustomSpeedLimit(laneInfo.m_speedLimit); - return ret; + return laneInfo.m_speedLimit; } laneIndex++; @@ -239,7 +228,7 @@ public float GetLockFreeGameSpeedLimit(ushort segmentId, byte laneIndex, uint la } float speedLimit = 0; - ushort?[] fastArray = Flags.laneSpeedLimitArray[segmentId]; + float?[] fastArray = Flags.laneSpeedLimitArray[segmentId]; if (fastArray != null && fastArray.Length > laneIndex && fastArray[laneIndex] != null) { speedLimit = ToGameSpeedLimit((ushort)fastArray[laneIndex]); } else { @@ -253,10 +242,10 @@ public float GetLockFreeGameSpeedLimit(ushort segmentId, byte laneIndex, uint la /// /// /// - public float ToGameSpeedLimit(ushort customSpeedLimit) { - if (customSpeedLimit == 0) - return MAX_SPEED; - return (float)customSpeedLimit / 50f; + public float ToGameSpeedLimit(float customSpeedLimit) { + return SpeedLimit.IsZero(customSpeedLimit) + ? MAX_SPEED + : customSpeedLimit; } /// @@ -264,7 +253,7 @@ public float ToGameSpeedLimit(ushort customSpeedLimit) { /// /// /// - public ushort LaneToCustomSpeedLimit(float laneSpeedLimit, bool roundToSignLimits=true) { + public ushort LaneToCustomSpeedLimit_Deprecated(float laneSpeedLimit, bool roundToSignLimits=true) { laneSpeedLimit /= 2f; // 1 == 100 km/h if (! roundToSignLimits) { @@ -299,19 +288,20 @@ public void FixCurrentSpeedLimits(NetInfo info) { #endif return; } - - if (!customizableNetInfos.Contains(info)) + if (!customizableNetInfos.Contains(info)) { return; - + } for (uint laneId = 1; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { - if (!Services.NetService.IsLaneValid(laneId)) + if (!Services.NetService.IsLaneValid(laneId)) { continue; - - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - NetInfo laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - if (laneInfo.name != info.name && (!childNetInfoNamesByCustomizableNetInfoName.ContainsKey(info.name) || !childNetInfoNamesByCustomizableNetInfoName[info.name].Contains(laneInfo.name))) + } + var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + var laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneInfo.name != info.name + && (!childNetInfoNamesByCustomizableNetInfoName.ContainsKey(info.name) + || !childNetInfoNamesByCustomizableNetInfoName[info.name].Contains(laneInfo.name))) { continue; - + } Flags.setLaneSpeedLimit(laneId, GetCustomSpeedLimit(laneId)); } } @@ -356,7 +346,7 @@ public void ClearCurrentSpeedLimits(NetInfo info) { /// the NetInfo of which the game default speed limit should be determined /// if true, custom speed limit are rounded to speed limits available as speed limit sign /// - public ushort GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = true) { + public float GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = true) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.GetVanillaNetInfoSpeedLimit: info is null!"); @@ -378,13 +368,8 @@ public ushort GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = return 0; } - /*if (! (info.m_netAI is RoadBaseAI)) - return 0;*/ - - //string infoName = ((RoadBaseAI)info.m_netAI).m_info.name; string infoName = info.name; - float[] vanillaSpeedLimits; - if (!vanillaLaneSpeedLimitsByNetInfoName.TryGetValue(infoName, out vanillaSpeedLimits)) { + if (!vanillaLaneSpeedLimitsByNetInfoName.TryGetValue(infoName, out var vanillaSpeedLimits)) { return 0; } @@ -395,18 +380,15 @@ public ushort GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = } } - if (maxSpeedLimit == null) - return 0; - - return LaneToCustomSpeedLimit((float)maxSpeedLimit, roundToSignLimits); + return maxSpeedLimit ?? 0; } /// /// Determines the custom speed limit of the given NetInfo. /// /// the NetInfo of which the custom speed limit should be determined - /// - public int GetCustomNetInfoSpeedLimitIndex(NetInfo info) { + /// -1 if no custom speed limit was set + public float GetCustomNetInfoSpeedLimit(NetInfo info) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.SetCustomNetInfoSpeedLimitIndex: info is null!"); @@ -421,27 +403,19 @@ public int GetCustomNetInfoSpeedLimitIndex(NetInfo info) { return -1; } - /*if (!(info.m_netAI is RoadBaseAI)) - return -1;*/ - - //string infoName = ((RoadBaseAI)info.m_netAI).m_info.name; - string infoName = info.name; - int speedLimitIndex; - if (!CustomLaneSpeedLimitIndexByNetInfoName.TryGetValue(infoName, out speedLimitIndex)) { - return SpeedLimitDef.FindKmphInList( - AvailableSpeedLimits, - GetVanillaNetInfoSpeedLimit(info, true)); - } - - return speedLimitIndex; + var infoName = info.name; + float speedLimit; + return !CustomLaneSpeedLimitByNetInfoName.TryGetValue(infoName, out speedLimit) + ? GetVanillaNetInfoSpeedLimit(info, true) + : speedLimit; } /// /// Sets the custom speed limit of the given NetInfo. /// /// the NetInfo for which the custom speed limit should be set - /// - public void SetCustomNetInfoSpeedLimitIndex(NetInfo info, int customSpeedLimitIndex) { + /// The speed value to set in game speed units + public void SetCustomNetInfoSpeedLimit(NetInfo info, float customSpeedLimit) { if (info == null) { #if DEBUG Log.Warning($"SetCustomNetInfoSpeedLimitIndex: info is null!"); @@ -456,15 +430,10 @@ public void SetCustomNetInfoSpeedLimitIndex(NetInfo info, int customSpeedLimitIn return; } - /*if (!(info.m_netAI is RoadBaseAI)) - return;*/ - - /*RoadBaseAI baseAI = (RoadBaseAI)info.m_netAI; - string infoName = baseAI.m_info.name;*/ string infoName = info.name; - CustomLaneSpeedLimitIndexByNetInfoName[infoName] = customSpeedLimitIndex; + CustomLaneSpeedLimitByNetInfoName[infoName] = customSpeedLimit; - float gameSpeedLimit = ToGameSpeedLimit(AvailableSpeedLimits[customSpeedLimitIndex].Kmph); + float gameSpeedLimit = ToGameSpeedLimit(customSpeedLimit); // save speed limit in all NetInfos Log._Debug($"Updating parent NetInfo {infoName}: Setting speed limit to {gameSpeedLimit}"); @@ -472,11 +441,11 @@ public void SetCustomNetInfoSpeedLimitIndex(NetInfo info, int customSpeedLimitIn List childNetInfoNames; if (childNetInfoNamesByCustomizableNetInfoName.TryGetValue(infoName, out childNetInfoNames)) { - foreach (string childNetInfoName in childNetInfoNames) { + foreach (var childNetInfoName in childNetInfoNames) { NetInfo childNetInfo; if (NetInfoByName.TryGetValue(childNetInfoName, out childNetInfo)) { Log._Debug($"Updating child NetInfo {childNetInfoName}: Setting speed limit to {gameSpeedLimit}"); - CustomLaneSpeedLimitIndexByNetInfoName[childNetInfoName] = customSpeedLimitIndex; + CustomLaneSpeedLimitByNetInfoName[childNetInfoName] = customSpeedLimit; UpdateNetInfoGameSpeedLimit(childNetInfo, gameSpeedLimit); } } @@ -520,8 +489,9 @@ private void UpdateNetInfoGameSpeedLimit(NetInfo info, float gameSpeedLimit) { /// /// /// - public ushort VehicleToCustomSpeed(float vehicleSpeed) { - return LaneToCustomSpeedLimit(vehicleSpeed / 8f, false); + public float VehicleToCustomSpeed_Deprecated(float vehicleSpeed) { + // return LaneToCustomSpeedLimit(vehicleSpeed / 8f, false); + return vehicleSpeed; } /// @@ -533,11 +503,11 @@ public ushort VehicleToCustomSpeed(float vehicleSpeed) { /// /// /// - public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, ushort speedLimit) { + public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, float speedLimit) { if (!MayHaveCustomSpeedLimits(laneInfo)) { return false; } - if (!SpeedLimitDef.DoesListContainKmph(AvailableSpeedLimits, speedLimit)) { + if (!SpeedLimit.IsValidRange(speedLimit)) { return false; } if (!Services.NetService.IsLaneValid(laneId)) { @@ -555,15 +525,15 @@ public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInf /// /// /// - public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, ushort speedLimit) { + public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, float speedLimit) { if (!MayHaveCustomSpeedLimits(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId])) { return false; } - if (!SpeedLimitDef.DoesListContainKmph(AvailableSpeedLimits, speedLimit)) { + if (!SpeedLimit.IsValidRange(speedLimit)) { return false; } - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (segmentInfo == null) { #if DEBUG @@ -584,11 +554,12 @@ public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, ushort s while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; NetInfo.Direction d = laneInfo.m_finalDirection; - if (d != finalDir) + if (d != finalDir) { goto nextIter; - if (!MayHaveCustomSpeedLimits(laneInfo)) + } + if (!MayHaveCustomSpeedLimits(laneInfo)) { goto nextIter; - + } #if DEBUG Log._Debug($"SpeedLimitManager: Setting speed limit of lane {curLaneId} to {speedLimit}"); #endif @@ -616,7 +587,7 @@ public override void OnBeforeLoadData() { vanillaLaneSpeedLimitsByNetInfoName.Clear(); customizableNetInfos.Clear(); - CustomLaneSpeedLimitIndexByNetInfoName.Clear(); + CustomLaneSpeedLimitByNetInfoName.Clear(); childNetInfoNamesByCustomizableNetInfoName.Clear(); NetInfoByName.Clear(); @@ -759,8 +730,8 @@ protected override void HandleInvalidSegment(SegmentGeometry geometry) { uint curLaneId = Singleton.instance.m_segments.m_buffer[geometry.SegmentId].m_lanes; int laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - ushort? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); + // NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + // float? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); Flags.setLaneSpeedLimit(curLaneId, null); @@ -779,21 +750,26 @@ public bool LoadData(List data) { foreach (Configuration.LaneSpeedLimit laneSpeedLimit in data) { try { if (!Services.NetService.IsLaneValid(laneSpeedLimit.laneId)) { - Log._Debug($"SpeedLimitManager.LoadData: Skipping lane {laneSpeedLimit.laneId}: Lane is invalid"); + Log._Debug($"SpeedLimitManager.LoadData: Skipping lane {laneSpeedLimit.laneId}: " + + $"Lane is invalid"); continue; } ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneSpeedLimit.laneId].m_segment; NetInfo info = Singleton.instance.m_segments.m_buffer[segmentId].Info; - int customSpeedLimitIndex = GetCustomNetInfoSpeedLimitIndex(info); - Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: Custom speed limit index of segment {segmentId} info ({info}, name={info?.name}, lanes={info?.m_lanes} is {customSpeedLimitIndex}"); - if (customSpeedLimitIndex < 0 - || AvailableSpeedLimits[customSpeedLimitIndex].Kmph != laneSpeedLimit.speedLimit) { + var customSpeedLimit = GetCustomNetInfoSpeedLimit(info); + Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: " + + $"Custom speed limit of segment {segmentId} info ({info}, name={info?.name}, " + + $"lanes={info?.m_lanes} is {customSpeedLimit}"); + if (customSpeedLimit < 0) { // lane speed limit differs from default speed limit - Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); + Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: " + + $"lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, laneSpeedLimit.speedLimit); } else { - Log._Debug($"SpeedLimitManager.LoadData: Skipping lane speed limit of lane {laneSpeedLimit.laneId} ({laneSpeedLimit.speedLimit})"); + Log._Debug($"SpeedLimitManager.LoadData: " + + $"Skipping lane speed limit of lane {laneSpeedLimit.laneId} " + + $"({laneSpeedLimit.speedLimit})"); } } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save @@ -806,7 +782,7 @@ public bool LoadData(List data) { List ICustomDataManager>.SaveData(ref bool success) { List ret = new List(); - foreach (KeyValuePair e in Flags.getAllLaneSpeedLimits()) { + foreach (var e in Flags.getAllLaneSpeedLimits()) { try { Configuration.LaneSpeedLimit laneSpeedLimit = new Configuration.LaneSpeedLimit(e.Key, e.Value); Log._Debug($"Saving speed limit of lane {laneSpeedLimit.laneId}: {laneSpeedLimit.speedLimit}"); @@ -820,28 +796,25 @@ public bool LoadData(List data) { } public bool LoadData(Dictionary data) { - bool success = true; + const bool success = true; Log.Info($"Loading custom default speed limit data. {data.Count} elements"); - foreach (KeyValuePair e in data) { - NetInfo netInfo = null; - if (!NetInfoByName.TryGetValue(e.Key, out netInfo)) + foreach (var e in data) { + if (!NetInfoByName.TryGetValue(e.Key, out var netInfo)) { continue; + } - ushort customSpeedLimit = LaneToCustomSpeedLimit(e.Value, true); - int customSpeedLimitIndex = SpeedLimitDef.FindKmphInList(AvailableSpeedLimits, customSpeedLimit); - if (customSpeedLimitIndex >= 0) { - SetCustomNetInfoSpeedLimitIndex(netInfo, customSpeedLimitIndex); + if (e.Value >= 0f) { + SetCustomNetInfoSpeedLimit(netInfo, e.Value); } } return success; } Dictionary ICustomDataManager>.SaveData(ref bool success) { - Dictionary ret = new Dictionary(); - foreach (KeyValuePair e in CustomLaneSpeedLimitIndexByNetInfoName) { + var ret = new Dictionary(); + foreach (var e in CustomLaneSpeedLimitByNetInfoName) { try { - ushort customSpeedLimit = AvailableSpeedLimits[e.Value].Kmph; - float gameSpeedLimit = ToGameSpeedLimit(customSpeedLimit); + float gameSpeedLimit = ToGameSpeedLimit(e.Value); ret.Add(e.Key, gameSpeedLimit); } catch (Exception ex) { diff --git a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs index 7d13a3803..7e2181767 100644 --- a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs @@ -1387,14 +1387,18 @@ public int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleSt if (bestStaySpeedDiff < 0 && bestOptSpeedDiff > bestStaySpeedDiff) { // found a lane change that improves vehicle speed //float improvement = 100f * ((bestOptSpeedDiff - bestStaySpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); - ushort optImprovementInKmH = SpeedLimitManager.Instance.LaneToCustomSpeedLimit(bestOptSpeedDiff - bestStaySpeedDiff, false); - float speedDiff = Mathf.Abs(bestOptMeanSpeed - vehicleCurSpeed); + var speedDiff = Mathf.Abs(bestOptMeanSpeed - vehicleCurSpeed); + var optImprovementSpeed = SpeedLimit.ToKmphRounded(bestOptSpeedDiff - bestStaySpeedDiff); #if DEBUG if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): a lane change for speed improvement is possible. optImprovementInKmH={optImprovementInKmH} km/h speedDiff={speedDiff} (bestOptMeanSpeed={bestOptMeanSpeed}, vehicleCurVelocity={vehicleCurSpeed}, foundSafeLaneChange={foundSafeLaneChange})"); + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): " + + $"a lane change for speed improvement is possible. " + + $"optImprovementInKmH={optImprovementSpeed} km/h speedDiff={speedDiff} " + + $"(bestOptMeanSpeed={bestOptMeanSpeed}, vehicleCurVelocity={vehicleCurSpeed}, " + + $"foundSafeLaneChange={foundSafeLaneChange})"); } #endif - if (optImprovementInKmH >= vehicleState.minSafeSpeedImprovement && + if (optImprovementSpeed >= vehicleState.minSafeSpeedImprovement && (foundSafeLaneChange || (speedDiff <= vehicleState.maxUnsafeSpeedDiff)) ) { // speed improvement is significant diff --git a/TLM/TLM/Resources/105.png b/TLM/TLM/Resources/105.png new file mode 100644 index 0000000000000000000000000000000000000000..fb0ca66fa201eaf63a405f169b363ed326f09f2d GIT binary patch literal 12968 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/110.png b/TLM/TLM/Resources/110.png index fdf3a16c7c8a3624ba4fc97fe5f67c2b0fd39bb7..28f2b0f931c2f35dad516f25ff22f6e418b48dc7 100644 GIT binary patch literal 11112 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 S3KVd?!pKQ0OI1mj2LB%hK+TQ- literal 82179 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

l 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 diff --git a/TLM/TLM/Resources/115.png b/TLM/TLM/Resources/115.png new file mode 100644 index 0000000000000000000000000000000000000000..f61ec322f64de4de00f3a6d5568eee0855b6f67a GIT binary patch literal 11128 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/12.png b/TLM/TLM/Resources/12.png deleted file mode 100644 index 2f508a84799615d0eed7f6193eaaade15d42c040..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9771 zcmY+qbx<5n*ewh!?(VWk@Zhcs!Cis{4-h=qCb%r_8Z5XI9D)URm&M&(0t6PfkKg;= z`flAndb*~otLN#SnbW7Ab0Rd<w+RxogT>p%Bjfft|G|M%920#;O&McIVMCFJFa_qxD)Ya#{9>VvhN zzJT4$U9I4p&27JeIlfwffqWd?90Jd3Xoqlc?@|{LR9NPL)p|ocpLvbYiaX z;gif~rOM_d0%LCljRcL@*4|?IQpW@bZ}EyDJT-61y!LsZZF8A;75@mW`%QDPqmb6W z<6n7w4eTNBql&69WI;+SNC8#(U#S+`AB3>*3@&*M1< zlcHB*S7W^AsD#WKxgZDv5;|dN7oYo?k<0V-Le#&V!6* zBqv&BJWT2!16Y`=M8|Ls^kNYXQRg{a?T-^Er*aUL>sBi124`ne7xd@`VH`|GVOURu zUzMo;PiQk6x`2UVUCM#6#dLsMLK3=y=$N6IKsZ4u!tT}RkZgL<9@X)#6NzZg$1s!^GPpwn+s<5&F%uFA+)z=(5U zw~U1udvruCx7@%;8d?aj+%SGD3l2p&`G>l~g0y2(;4?<=tKGVIHo@$E6xpr%Ozh^m0s~3%I z@}3n*EP&(!alMP)BYB@jETl>3`a0R;!$MzGXREeI<=tN~2Y7lT~VMF&RDPfScm1bW%Oj!rMFPQ%jIb?6<6c!Dmq zH~(h)hwBms#RaOwt1f=ulj+-wC{BCA#J(Of*XmKh5CT##ulLw{WTyb?q~r(~RZ!)& z6BTMi!G{_VT#E#5B2TLaRY~`Xw&uu-NMd2PL%_c|!18#)qX41dci?hfnoYhAkMqR! z`nb)21LS^xOLLJdCI(6$0%e&B-?!h}H%*Oh-D$wLR!_)WG+8trz7E7FKmSx1*A{Os zBsd&@cO1`^s>Zncc`7#DKWSGXJ(wt-CLZlA7Ls+=Yl4J#JhF|oX@EKD)uw&o%n2js z?R$dG#6N}IipS{Y$VSRsF61RsTs#s_RS?iF zaE8#+XyU%IT=Y`Ds?Ta#b zt7Rm2=2GKiSt%jE48pXi|iWU%?%T~LLNv%W! z=lXi@KI8OnUTE=(SKujVW8k{DxvA0#%Lia#5v}-Km9Q`*d`Es^=5Ppl;1FxUFCX6o zW^%0^Xbfx>Y&tXCwJdnOBG+@dEf@E2z*} z@IP>OZQ+9rrHla=W=EUQSY5O=)k_ek8TMF+a4kNM{J!0`l!=C1;` AJnuUpl=WA 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 f{qe8%6ISvB-zoaryFAb(2PPw-C|)CK9P z=geArtsi!zl7bWn0UrSj3=AYAEv^E*j{f(6g8`naw5oG}7jPF9DN(TMX~I+B4V0yb zya*UrT^!<@F*NWV-cefH1^Dsue-H3@haXwMhp?_P@)EGS;Ha2vgmRVd+rUS-t`b_V zsty*e9>&h*V2;Mt_O8tK=B}jd%&g3u?+WlIU|?L=GU6g?o(7lt@ID~T*NnN3n zbPH}Gv=6m!IcONk7llcOu32X?bg0v0lMO;f7F_B4aevRG)3`h6f3S1I^&*e%7ybS4 zyHnh)_$J1aBpx=SClLZ#G@2|L1FB9^nv`0(nl%q=x!?PXc9+**6Vu_J#)vq~W(1tJ zMy#x?7xTrkliIsFn+G#OpNCI_UCJ=yqUp4mwz$Z;MoG4rBxub#wMN`g7zF4Y>SZcN zQ;(M$EZ18d9uEQFkgJPjs%zuP4BD=)>=Todzo_hpm8i!J>P(uZ|{dA6Pc8imDP*u_g3jL@=*kv>-S7Yeh&Av zA*{j>7&EN-Bz1E~dcyEWzsY%WB4QItqV2N{_IzA_eq7H3x2*d8enx05mBrA4%YHNd zC{^g|!o1dcj_<{!V#R7C&%M@L=`G_+dZ6v+fWYdNg`Jk?7BwS3ZCdDx{baulS3TKB z%x8>W@XposVz9&$!RWQqe(;aCoYp5MdNCFwzMQ?)I@7gW|Brth`~*45ej5A@(1@Z& z$ED)wU(Jc{o}NBGK3=b9viLlhaa0XPyMH8YwRE}w%Rlb>KYdbgubdP6{KFXcR48 zpRQAjDTZSX7cTCq;Zp7nW*D8`HZFu)3~Z&A+HGADRBUDE>h&5?>%>64>IJt->79M? zpxXcQXQ_@^tmfd{T-<>Bfak_Ltd9xSJBH^II>}9>Fttplv;DbSuF}0Hlr0(|w)ph> zpyFe9#NAk_<3p@iQt}E;y-CG~>_#Bp^Gdv1f#K}eSVh`E0pSlTFg-^Vj}WEgUb*+; zY&6@M9%jk{;k$}ph&IvhgpI)Y4MXVn)nF)yfYYqN+i7Cj=vh+bE8oY59`;-T(FU3) z31XERMM(t(W)X{0Gs#SlWq-pS-fbm$FV_tk=#DLu57aINmfUqXs#?ceyYcbB@8@K>I-K^h(_%W8cvE@HrojE*xI7L^iekVe%B%KA+7W zNKQ*jyFg-WwRXIJgLZ#zQf92Q5X08V*Xg?wZTt39b5sgz0=mwI)%o*obGRBX3Ewq1>0W(`IJ z{Z4^jfB4N&UD>tMj!hsh96Y?S8qH3geGz^AE<7gHG%l~}Nm)b$T$`7(Q})Z$Z6Qsq zevFPZXaL`^HO?nXCGeHg??HSI7uxNs*=W3KDMec~V_Jmd(RgySUXPz|3N7c^&P=z_ zo?GzR&Jko4Ygj^v!`Mg!&VXn+b}7N4@>;z)McFi}jBTG0CX;f(U;wjzyBQ752C%PN zn%`nwciE8`>qaGGXqHM0z~x@sJ;BvLE{5XE$jDT>{F&AsipKE=ccWD=hW4N+1vV)m zE(NMN?gRbiDp&LGU{@mjPoj6J>VC_l3)49ui%|2#Ne@6d?`O@D*awI&U7MCxVyW%wMvg*HW53dNpTuI zQR;NU4;@fdex$EN1;7@SJlFaA9J(Snry-S1QKqNl;{Va^v!iy#t0P z4jb21L+((^=EfXn_odnjFAi@6A7MMCHDfYhfjD6({>m-MznO6aq~;)FZnOW7aow^3Ag zpGUovPEVelE)TygNq46SPWQ;}#MA-zy2Wg?tmprobGM}xW@Vqa73V6_yAUad5rU7@ z_fmpvABxNIOfE}@D1M_2IDev$clE}0s* zIF!n*o!IO6u`Fr`*>0LT;8B<4BD6OXRKuPl|1gmFP% zNBs~|5ltt6x^auiDx95NI8mifEVWv{V_lF{U1QP(*F~x2+b-<_-P1xsGu!V^qZB_% z5zfY4h?xk>A+`$NU29Pc+$1Z*Z~E=7eI_QBGAhupF{t1249KKDx$Rfa=L0quOp(!{ zmvP-Wt)?x5=gCnKd?OjlJ$+mDdf%kLgi*@a_f=IyodLLUft@gow$ruV-^98F5ll zIv9@e5YbH>^udMG)+})5sc%>-W9fd3QsmkG^~xtOzJji&ol9#Kz*0Rtc;_Pbydu8K zrz)c(q87@_2(8@fvE%!MuWcb1UAF#CnCcNC>p+dd6q1(O^7klf4{k=|e>XFB)~dr$q%jyENC@PuEEOjni;6dc0~`@>k>3K3DNlb0 zMUlV4asFW#ZBOVkF~Hrgv&Vw?_a!^a>A2E10ooFrWSE~X;#KYN`vG1rG6=W7UPcwc z{bDsxE>=j?I}xf27pMXxMoAgrf}wEMkE5x<_>_-CgxUO476F!h67ei?3SQx_%NQ&I zR&=Ko#hh+0Krv;T-@dO$>cR%`dR*BS4l*Rbwoy#w%nuPD>zDr`(HKq9a^zC!HY6){ z=lJ&>f{6_7{+rn^+sF{0oEr1*H{lURr?40bqH8~38U_0ezJFT2`0)OwrXcx|rO#t@ zE{u2u>(kjYVftGUR!kLbilkm!Jme8*xLZhceo>z4d!qc%GS}rnr8n|giP~m{B{`S% zJz}fJ!u}~KIgWSb(tr^nrYjARKiuwieuH(}?(!`Ig@E(o7jJ^=xT27(%oY@i1tkjq zBc@?lixKme3Fw_n0qVfvMLfd%zI?Ip57~@|jKaMrpv$-XceT}Fj!~@FCM{r{(NLOY zS4@B3N`#mLy__Z|4=Hv$KisR%F znObR47kAvb293L0!bG=j=toz-{8cIjKeloA>bMx zlKEkj5Nl|b$A@=j7;_EPsPrWuJ93ngqWrsu^LH8llplKqdi16MLKTKgXOLNYr`;-& z!N|OrlrkVO|0XSR5x-(TQE%Dj5!n8d;I6#3dP~OYew3i_NQFvxV78vp`pM)00Bz|F z^d1L0s{eJ#`Nmh2Uu`zIZ)^0*LpuWwal8A^M-*#o@rNyC110m-q0d)pW+<8B5-BeQ zHx~eaJ%z(^>{tEq!vPtQrzwsFkALao6?s3O~+@q10&_{0Q}Z+VZ#8|DdbU*2 z6_v^5&}>aAJil)q7H610=Wj8&cLY9r^ZvBc6M5D_IcI0ZWjUQC+w#l0;U4GttTf@A zc$GD5(0rPdfg$?+?S7_GrTANam{Hs`M6|LMW=+orFN0xs=gszyI{8e_i^4cT6J_OU zCO=Wn$jHe4ul!TM%=L`(aqWR%ue|PTobGY#2vwpD!WI_PnF8LARP|*kmalWT0o^4fcI~tTX8Hs!ODjMYukN zyLJ2V?_2A5Ql+4UZb~J?l;H4e2ijHkW zW(512S70D_i1_bzIRW9P5+H2)&tr^$61TS2@PaMv+Y|^EZivsk7}Nnbwclt2?frCR zUUca&%9*OvYQ6BA^F3=+iueH1Dgo_k_p#i~l|(E+Vq$mK$T*aH;4PqmuHF5TzH?dH zgi|h^bPTb3Q+U=?cq^KH4_UM!L7ya5Hfa_}7s7LQoH=WL0ZWA|OgzqEf;Q(^sG+%c znDOGH7$7?8$R(oS7zVVD!~23^5o_3jv-vzys%lXT?Ma0n*dSYB#Md2Ro%r3u=mpP# zfDE@&M9UtAnM9!DX0^=8H$=IB8_01Nc-{*qv9EyAOvk}y(xL1emQ%>#*KU?XGISt4 ztqcFx7Ef;18a#UyiQ+PIG?D?g_DC~F_se$Sx4iv1>BWz{Dq~1GT>srUL1&qN1Cua8 z3w2T0O!S?SD6B_6)64dUq7gr`A+cQ=9ElWhY3y2;c_GG{2A=&CMk&d2#?G z$xQqrIXn9=332gS;JWVu+O!)i>W-%}5o^ZcfT%vg;}dr*3N+e7W^?j=d0-742uGQ* zCj7w>FYE8X^}qlDsz9;)F9B;3guKv*hDQ!BvS@hz{!3+SQ~??1SEJ|8NPoD=CJWLE zomi9FZ8V$9rZNu+g6-`zVeSF_|EzCUKTWPr;*7iTk_IIVJ8xz81foX2_n`zkI~t)i zDX!w)6I>i?^tS`lf8yZmwI%A_1O0mmk++LM63*60AN@Ihe~5=G6}X)*XF7662Ceq7 z;4OEpdwKlW=2D~i7)SZX5f6_+eY0cy<=Pgq4?lzg(#17U@clvcpN+4ytgNXb|1Fdu zDdv{yAV3R_X>RnV!|cFEmEaoj_?1C1PX;61Ez6JKKx}Ba* z(=b05^}V&Un1V$&#&$=m6>H_xi)x7@@JC%{1V+%&ZBafwiUWmj3U(r&r}?TclcjJD zZ%0UQwsi;p-T#@;Jcj6JfCC*Khep3WBMcafHsRMTZh0Th;|4E}(db9EE76jY?#d%k zV->yCCeDyWBeB7tl zdu2obq_l6&15q3Z1r1#{D*fuDTg@K-)qEVzzk~`q>p{)SPng1`sG=C!mX;QD*dHdZ zM?gN+`pt^RY5!N&u7uoy*!>}nkV!YKgu^s0mDkhqlB;{3l*2&ta@Y#|akvj}lzN56 z?db|YYK(o7hc$qpW>{WgBF+j)^q_;7>-xT_#il&(mxAwM{Q}Z$tUov?G{Y%*_Up24 zo*kGQXe!C@8`^#7XP!v#bExof@q{g`PCfWs$LJ{0`{KQ`Oe`4{m4v2GVgu;#a4gBk z9iC0{F~ThbjY85aOruOi!YViA1pj|$fDnK3ola|DI?drLUp`vb_~N)L{5+JK z#zn7Idb8VTy>K{z+kx+$25g+)^x~t%4R0Q}ghr|GAT`^yv}#7KBkTr21+wkOW0rBn*(^JLUQjZF=GS>an6n>+Ksntp znk^vxm_ikE_a?J(vF!ToOgG%Of)Cg3p5}?VRzj6X_TQxy!9axUtPIB6W3v+peHCX8 zwG5O(4mLl`>=Y4-5{U>2dMD~2_$oXi7ldbt@S+2`)?kTJq1RSFE66m^-+x#^DeQS= zPYM+Gy+~3=!?%_bu4yF}N0nqWw$j-~UOKqteCbzZU^Hcb`yx0OW6x0SV)>L^p z39aEkb%IQaH&lN&NZ_vsoaD^Brl(tsuLx;&C@vnOV$I18%PC`=W7YwvC&>>U&V_3( z>J2Czhfo{F`+_NV46$8V&|z;NWV_uEdjl}|HcSw-4Ai%R?rdvZo&AbCAY1QOsqIWr zm09ufU5rV(3TR`{EEnTvk8kMozBF2}q-kLTpzdd7|0rGV{b z7$(RJJdLTJbJ6>{OVcV^zytv|*anT1-@%%cwB7L$=GqH9hA@|7C9R1>x5zWK@Pw z1LLxvnxeNm+#8o|NVk$-)}T|5c}UB%Jn&+@DH??PB>=$KAs`~&Uo-f|3|EjS{JoZb z50VHKk%EHc;g`Tlf5SkSSV(Vh3rod6{B<_9P?gzusCE}4fW_5s zr>Rz-7Iyj_m}S_~7oUR!7gHf(N1ASn9W40HDcc}9bU;D$KE$+fsPZ$kh6~lT0$?vk zG~4cjD?m^piCSOYfUsKI@&B9>yROrTy#)mmJ;uRj~h)aU=bEviWeDET*QWZm{Y>C-r5fErfkX zwb{0?){;e3n(#4V=LoYfqXuMGHozTHfNXQS%y0q~S%|s+D0yuzI}Q!{0wc0-BffYH z3pbMh1JRSmP#m$WT&>%p(Bfd6R*fZ8!diwS3qo*X}kjpoHm7Ux_DH*d6u%{bn^V{L>M zh)U8BvzLccA7e$b$CrcnX+WX5le?bIa=2SmRdh;-%)_eDUhD`bCgb=otoRgP2S%QE8bHj(L!u4qm%z|f)n0F2onl(P@bNP``;=W%Z4 zdt|oC$EwyIe2E;DWMVC@4C&*8sDbAoTdmwSR_#DzdQyPXaE0M67conXkY+9k1*TUq zD~$LC*GVD$;*}oQ7u9@gXIA)bBoSoS>fIAK9NIC{?lQ1-37$^A<9Ptew_X_tl$X6;X}UQXGl zDC_nCCd{D2mad77$w6dC>NBtyQ8x^Hi^G7K&T?RpM!W;pVL4@k_WMMYKL5k4nCNSx z?N)n}PbgHC)l|`TnE}UBrhU>Y`tK?;*Y#dW* zYmnrLfn;=Pb2mdU_5PoJ`XH#+wWq5s2E(H@m;$c)l{uEtr8F&5jFGa~#bGBLGt->N zgAdgj1A)vb*tZi)ir-!dUaob?UTRR3=Cc@%5qLt;tj@tC{(L`?H1m{V z`K=<~h6z6_xR%B>dOF8BRzY%QJS&$X6ix7wp%>)@%pSCc*=2}2>GeNX8+C!fN>3m4 zX~1F<-Z|bxJ|EAp=|0oN`JFUEZZ^3y!>^GLs|aktC~Q(Bwaj_rYY*Cg>vuYk1lAuO zZgP+a0o-r$L8^L1*+s~UAr7xVikF`{(XzNq*~KV%dx?+tLLYer$6!Q+UrkQHc`fe+ zO=s1)H-sHONML@Up`p1)_8jn|c$h9%YPaJ{U#>T4J3H6=U>yVoto{lyoByZG;H(+J zoIfha#H!##iOoQ9#AscVrNmdANRn5`yJZXLMG;s83hu`-fb`g?OwyM^Yd1QwruH{6pPdnaIqaK`q}Az6;so-L$^ zblVA+j;P~Ci$iLSGf{-PXk&FLBU96l`WyG<4x?}+DxG}32|#CqpO{7O5#xrfo(nFe ziQyk?h&@4_K^XKQ$T!uIb(tTtFG{-a+3+Y1Q^ftx^e zdc0V>OGf{KZM9ZkeAA+1vIQ|lx+v-q7IC=5b^w$i=q#zA^E5@qWOYrGddF3-{SdmaNy-3j#*hA7|65>2hTsPPbMIY?i*pUA8_5@% zjV1QKMwd>_A?}eam7TQch%VxumkwPa&0%3}84k9&{5jT3C7J$gOK3!+T>M!Pl7c}a zPh}_D&@{_yO$_P!U)L_4`;2zR)~f3FwpQD{3n-b|{jDvBiJK^z_@e;p6w-r%=FPLH zS{(~PMxnKWx}__Ap|}FkKS0ml?pyG^Q5?$N0B`l4f|~Au8P%lW7jN)4zX0kyP`-s8 zWnXkTVqe1aEETmaPG`IW>06@DmR~BDw5A@sMCG~#B^Qg{FIg8BJ!{gD zIFc1#15!9NamnKxts+=d#NSKS0koYZ0?dYfuYULvK3-xoeKt$ww=4f|5zrjOE?8Mme$Jvc{Eh1zgHdC_2^XJ;I7T%BKpwHb=3yB+|JbY z+#VxfLWKnc{ve5BJbyEWk#}YB7KcFm>w5<=W1?~?n6IHnZS83|a>W?T4$6y_iX+Yz z=3J&Zle`7Mnw^TG;V`n_SS?Kb->bWe=d3vG=4s5#SYRCxyIxXp^t}&Xw$pG?cID$7 zQ9Fx82)BMlSQCaSRcJ8n{r!hlCM2OW7+y)^m8(4k1H!*C#ji~Xn$oJpF+>On&r>!! zM$CU+#!RK6Uz9k`()N_dnjXe#T^9}j~eE|z2FEW7Ws6GsqjVBrkx{z$n39G26c#>Psaaf=`n1tl72I~Z*^-GMa z%=u1ICJvQds#u}m$Uufc(Tsm6~iy%E0^oK>_1_rhByvQ;5# zcD^NEQcKK`i=g0lQ|B~{aeLf~|J`LYdBRZ>5GbkbF9Q?v+v6h{Z;YsX z2K(3+p=fVLXo4YHWo60b8cXUi@8+5?=42MnLYWvhk@>&7qqUM;KYD;06c<&C2!V>N zjN_J_3^`3_UMA--A*u0BpxzYO4!ut4(8Y#BMxLh3bYnqA5D8v5I>RzC6W`P;YMh1h zV7)5lI-J)FM*~3x?5;OjQ*_4hMXoLX;%;NKIkWN0vp)_Ihjr5>;8vVq2FIrP70wK_bLjO@`Pqf zk{Xvn>VQ1;EE&j4`rZs=>kh27>#yG)PUa3GRE*&a`=19ptZvb@Zj~bu zSH-Ca)#4+oi*0Bj8Z2*363hv=HTq39OVnZ;iG%u59RQ{`R7vF7oBI{HVCnjc-&adJ)W3c2CxR2$oYAA(Iz#w%8-Z*w|3+=t( z&T3nOMq|yM*yyDiD;N+4K$rLEqSNNY#;xGQhvU^E0}LL)dr0gvLwVa@nGrF6WujBY zsdPqB$jQ7I0*hEuao5#h^OddC!Pz$(tyKpN+KRo4herWl#~-X5TVPfii{^3Tfu%B!TwlT?sX)HGaEE;L+M07IiU9?z)gjp`5G>&G^xTZpJ zUrj%s=qJWo-^HmAyW!1jrNpaB0ktKk%Xd%<5Ox4DXP7Lzy?L287-gx?Iel@2)7f0# z*ecZK;-aR_Wfj|pz|voP!i6pRY(0B$yp5L?(t=%PfJ9n_6Nbv^qzFIKl;*ZBXjIq( zEq{iOfZY<0uFV(%+Xn-Wj?UKd{S}00sX2-!OjBpETdtmHB>XJC}GT7#wROq?N*_yWCv7hs%9_r|^GQmEIR)2H_r4y_`EgpL|m zjA%vC+V4cyse%5|($Xw&@Z*dDG)L;M>r;c4_+{ryzH*4$@QZUF3IcMcI)QLrYF|^p zW@)Y60x7j!lnVjIkuw~L+P7`B)^<~;AUEP&KQp75WWO6CJ!)k_oj=~8X`HUi#{BxmgDLyjF+lko4b zPw5iX1Va9UBNV}5K@S5`lqG3%JHPO_9^6uj@lmvI80)KdjGR8b<+U`!P<0qEdgtQ~ zkLkmx88oYZ)`eT{{rR(^?VBf%*aHZ&KT$D5J-v4+87mSziA#f@=MJW-{OQkgts897 zQT7LQtW;CVq?C&QT4lohYEqskN@P9&vSQ946Ube-K!hf9dv`*sLNvAo20rc!IFVHKWaTv+lkFR`eY}xP zX;%ETaHA_cav9_5t;y_Ix5Bz9OyT_CpC7L*=&To^*ik(B{uijFWuj2NZHvj z8#HzjA3qpmW?z*nfY$5w?9h@cF=;JFgNXm0ip&HBf>YnvlDyeR#}VlU&mE#aJnZ^U ztr%e2I7jAGZ676EsM2krn9ZUT*y$?-y23<@rbETOB{3%-is%x2`0=4f{fqEsbdeKS zbjHE%=dVxMH0SoP)_-hlv&?=~mIXq>nrz;dQu6E!m}SYANZLFzTPH6;`f73{C9R`$r~HX>xM% zSKnXZcJvNn*-N0IZ-xLX+nqd)pgHqh_F9O=;xiBtxYpm38QF4&w}4%5JF+kqD-Yre zqRHc8O~m*Lqn{a^Y|JWyuIC>Jz)Sk8rFqoS0v%r_i+hQBnx38>flg&0pGn4~I}V13 z&z;}>XdLSw8f!yxA94@7-nx*>jQ4c)n?-FE|mg-?QOm^5=|sqxyo5CWiu6!Q*0Xi!4Rx@;o7rzdtvPI=(32h_q63#kjV zT=xs5=!@k{{msVYtpYAh5!{kpyc3`(;#hC)c=ud*ui zo=UOpUcMKrb?Mx>qJ2{?f3($dKMYo+g)cbzN{F3=Ml5H{0mIzg#bY-N;0eA(^2Fv( zvgQ*aB6T42&1t%2oqrT&E6QG>gm3e-&1eCve-4uX1!{@$&-;E8`U?GN6?E zVah|TBaY-fU=r`o%IA>B%nye8YC{?VE@#~*j~_obVl8imanMLyl(q^xV3|r;0qL%? zKq5Le<;Z2kXc(5n$7rE%kmm0!qS*6>{E4^&*~_U~{z*&t3jFVHzOhs+@6*(oVI^P$ ziK#xaoPYl`-_}7Ja2tGWu9aH+myZ*+ywD{ z2IZ|JRrrc^zX@#5U?L9FK>}nt4oltSBD*`oJ3h*aqd8&lcPErh#Q*ECC*bVGHoPaX zsAvQ)o`tbC%LlA`IBY>)qgapk6T*8RGn$xkr{$oDZWG-E}ic4X-x z|F`!xpSkmkGmeY5_Lz(P6uS!*E8G~A|EDWaQ~W;s`G0-=-HWu`N9_{&6;EzPli>34 zoL8$X;h<7~9t)tm<9s!{Z4RMTIyG3cT$V)#zZePd{2m$Q^OxAyuDdVdqxpSX$5zTn z-%kk@nR34faOv+kJjcA?Z$dup3*OeHhG%Tj6jvyD++=Qam(TWRb03eQI2hq8B6cd* z@eG#`L`o18Fnd64kEnPY<@D6-rd#Ipsa1c~H5Uo~AmVi$ZT0$Fe!&cX>Lj(A!%1`+ zL+9Qqu|s}_{%>J1UURe#?gS$Oqn9Z_8unD0UK@=W)Ex3NW^2b1>h61BP3j=0AJ) z>(+V~fAV-g@Z6Zx+n_CPmUsJt8)}_dS=!4COl zU1dI6+LPC@BpSeA49$7_c|R>z=G^%O>{@~BNnoi@Xu)EgQbQ@gRmu4gEmyX7mw`5_ zQ&(CcoYBk1XasM5=inz?P23TEz@UwwPaLn(^#`Pqru3PlTR7cjfr`4$7xcg5DNG$G zZ2?R04Om$(CUvfaStwAa|Je6*?^t667IH42^6cXoT3k=(UzIFXmbAYhN~3dvw2uY` z29D^*pbK;`hUNEZa_(x8zh4geQUV6@8zvvdC=-tHgc60M|wrSe}7$+0L1-n$t`Jh zv%Uq|i$8y!905PLB9nf*8sI=D#YLuL!x&aPPCxD<(+#gd6AZpaAMTm*r9;>-?DS;q zc-r<>RMvMD&z4puQ)-)V z5q9<&dVVT;`ccFCARa(Qo1OCj}bG$8xZ*ll;FYZd^m@RHhthU5Ct>bQnW^`KJ4 z%ivc!BvKzR!(N&jkPnohHF)mF-xb4)7_RMJk@VF?HOSbOU0o{;06O5!@ZV1!8TyS0 z{FaT$#9_HZB8gmlN2u6}02I)(SUuT3wu7|+8?NK-0L5JbG>saAnA(R4fWuxG8(HQa z7zyllT=KqZv($*jW?Bsc5gghyLyKzK&yjv^*7V#rth5+!ly}}xp{oUw3J~O=%|uxW zwj6t0L0)z{Lf;Viz!@egLYnR9&GIZu*Vw=k)93RU6&^Tw$N(VF!vii4N^9tvsQ8ayKBrX{U19 ztq(3*rduWCIR2vYz2%60-C?i=*cpD^Q4O#AJ10bw-cF|r(f|KgkZs)SPQ?Rb^csg3 zwRZr^m+k-ud=8sN_-^u)paaHD?cR-AeIujYLt+6hLU`ab0YHq_%MFfl`(_kht&OPo z=?ujxto?8QqAgz)m40=12SsAblrhI&S$evCl^0*w!5vOG1pbq;o2xObj6FlkokPCfb zH=Y!4?zu1Y@nYwigXm_Te?iQpOX}bit&67p%()&P5V-kU;C%-pVjl9clK0j7uHG2Z zHc-BnsfX?0_9vUgq|w7<%gI`5FYHa|1qLGaMFH1s9N;uDe0f+6=RhNr1solUv7DPhIu!Bk$ZXn6UmOSy()PR@_JNU?eVaf5*1B26$@- zqA<{sU!;YFdvBWUHkiHlBWVg?kqJkCD`byrRO^jFTRT`xO6SS_>M|KZut&5F`kzrO z*4+7np%KF%)4ARv(zeQkt@MGzY-q{!>N*NP%ME+{fFSR}?P^*B*xi4+{M6LcJ5KTj zy-lsKSE1hf+q978`EtMHIg!GdYGs;1oI2W~jGk2Es3fE&g-UQvf@m@bqS3$!Iaw2h zYW0fcHQ>~P3p+9QPJslX!W-8I$iHxha<8RTvK#xUx64s+lfYaHIAI6ICbfK#01XJt iTD7|e&ollXB#pwczYmlYj)3EVU@{U4;?<%?LH{3J$mfdy literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/140.png b/TLM/TLM/Resources/140.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf5aabb761c5196bae5f6a11416b79f1e2c7fb4 GIT binary patch literal 13824 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/15.png b/TLM/TLM/Resources/15.png new file mode 100644 index 0000000000000000000000000000000000000000..120fed7b768553cf82fd76c3a5ce3ffb65de89da GIT binary patch literal 12960 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%0A>d`t0{@;_oZT{^pTW9`4P!!m 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/State/Configuration.cs b/TLM/TLM/State/Configuration.cs index a570bd888..a0980b3ee 100644 --- a/TLM/TLM/State/Configuration.cs +++ b/TLM/TLM/State/Configuration.cs @@ -12,9 +12,9 @@ public class Configuration { [Serializable] public class LaneSpeedLimit { public uint laneId; - public ushort speedLimit; + public float speedLimit; - public LaneSpeedLimit(uint laneId, ushort speedLimit) { + public LaneSpeedLimit(uint laneId, float speedLimit) { this.laneId = laneId; this.speedLimit = speedLimit; } diff --git a/TLM/TLM/State/Flags.cs b/TLM/TLM/State/Flags.cs index 1bbd7529e..cd1ffdb5e 100644 --- a/TLM/TLM/State/Flags.cs +++ b/TLM/TLM/State/Flags.cs @@ -52,9 +52,9 @@ public enum LaneArrowChangeResult { ///

/// For each lane: Defines the currently set speed limit /// - private static Dictionary laneSpeedLimit = null; // TODO remove + private static Dictionary laneSpeedLimit = null; // TODO remove - internal static ushort?[][] laneSpeedLimitArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index + internal static float?[][] laneSpeedLimitArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index /// /// For each lane: Defines the lane arrows which are set in highway rule mode (they are not saved) @@ -445,14 +445,15 @@ internal static bool CheckLane(uint laneId) { // TODO refactor return true; } - public static void setLaneSpeedLimit(uint laneId, ushort? speedLimit) { - if (!CheckLane(laneId)) + public static void setLaneSpeedLimit(uint laneId, float? speedLimit) { + if (!CheckLane(laneId)) { return; + } - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + var curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; uint laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { if (curLaneId == laneId) { @@ -468,35 +469,42 @@ public static void removeLaneSpeedLimit(uint laneId) { setLaneSpeedLimit(laneId, null); } - public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, ushort speedLimit) { - if (segmentId <= 0 || laneIndex < 0) + public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, float speedLimit) { + if (segmentId <= 0 || laneIndex < 0) { return; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + } + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & + (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { return; - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + } + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (laneIndex >= segmentInfo.m_lanes.Length) { return; } // find the lane id - uint laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - for (int i = 0; i < laneIndex; ++i) { - if (laneId == 0) + var laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + for (var i = 0; i < laneIndex; ++i) { + if (laneId == 0) { return; // no valid lane found + } laneId = Singleton.instance.m_lanes.m_buffer[laneId].m_nextLane; } setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); } - public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint laneId, ushort? speedLimit) { - if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) + public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint laneId, float? speedLimit) { + if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) { return; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + } + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { return; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + } + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) { return; - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + } + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (laneIndex >= segmentInfo.m_lanes.Length) { return; } @@ -510,21 +518,23 @@ public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint lane if (speedLimit == null) { laneSpeedLimit.Remove(laneId); - if (laneSpeedLimitArray[segmentId] == null) + if (laneSpeedLimitArray[segmentId] == null) { return; - if (laneIndex >= laneSpeedLimitArray[segmentId].Length) + } + if (laneIndex >= laneSpeedLimitArray[segmentId].Length) { return; + } laneSpeedLimitArray[segmentId][laneIndex] = null; } else { - laneSpeedLimit[laneId] = (ushort)speedLimit; + laneSpeedLimit[laneId] = speedLimit.Value; // save speed limit into the fast-access array. // (1) ensure that the array is defined and large enough if (laneSpeedLimitArray[segmentId] == null) { - laneSpeedLimitArray[segmentId] = new ushort?[segmentInfo.m_lanes.Length]; + laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) { var oldArray = laneSpeedLimitArray[segmentId]; - laneSpeedLimitArray[segmentId] = new ushort?[segmentInfo.m_lanes.Length]; + laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; Array.Copy(oldArray, laneSpeedLimitArray[segmentId], oldArray.Length); } // (2) insert the custom speed limit @@ -745,12 +755,11 @@ internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { return false; } - public static ushort? getLaneSpeedLimit(uint laneId) { + public static float? getLaneSpeedLimit(uint laneId) { try { Monitor.Enter(laneSpeedLimitLock); - ushort speedLimit; - if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out speedLimit)) { + if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out var speedLimit)) { return null; } @@ -760,13 +769,12 @@ internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { } } - internal static IDictionary getAllLaneSpeedLimits() { - IDictionary ret = new Dictionary(); + internal static IDictionary getAllLaneSpeedLimits() { + IDictionary ret = new Dictionary(); try { Monitor.Enter(laneSpeedLimitLock); - ret = new Dictionary(laneSpeedLimit); - + ret = new Dictionary(laneSpeedLimit); } finally { Monitor.Exit(laneSpeedLimitLock); } @@ -976,8 +984,8 @@ internal static void OnLevelUnloading() { static Flags() { laneConnections = new uint[NetManager.MAX_LANE_COUNT][][]; - laneSpeedLimitArray = new ushort?[NetManager.MAX_SEGMENT_COUNT][]; - laneSpeedLimit = new Dictionary(); + laneSpeedLimitArray = new float?[NetManager.MAX_SEGMENT_COUNT][]; + laneSpeedLimit = new Dictionary(); laneAllowedVehicleTypesArray = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][]; laneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; highwayLaneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 2a0da6459..463cae0b6 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -186,7 +186,7 @@ - + @@ -366,7 +366,6 @@ - @@ -465,6 +464,20 @@ + + + + + + + + + + + + + + mkdir "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)" diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs new file mode 100644 index 000000000..f6bcb1908 --- /dev/null +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using TrafficManager.State; +using TrafficManager.UI; +using UnityEngine; + +namespace TrafficManager.Traffic.Data { + public enum SpeedUnit { + CurrentlyConfigured, // Currently selected in the options menu + Kmph, + Mph + } + + /// + /// Defines a speed limit value with default Kmph and display value of Mph + /// for when the option is set to display Mph. The engine still uses kmph. + /// + public struct SpeedLimit { + private const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h + private const ushort LOWER_KMPH = 10; + public const ushort UPPER_KMPH = 140; + private const ushort KMPH_STEP = 10; + + private const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph + private const ushort LOWER_MPH = 5; + private const ushort UPPER_MPH = 85; + private const ushort MPH_STEP = 5; + + private const float LOWER_SPEED = 0.1f; + private const float UPPER_SPEED = 2 * 10.0f; // 1000 km/h + + /// + /// Produces list of speed limits to offer user in the palette + /// + /// What kind of speed limit list is required + /// List from smallest to largest speed with the given unit. Zero (no limit) is not added to the list. + /// The values are in-game speeds as float. + public static List EnumerateSpeedLimits(SpeedUnit unit) { + var result = new List(); + switch (unit) { + case SpeedUnit.Kmph: + for (var km = LOWER_KMPH; km <= UPPER_KMPH; km += KMPH_STEP) { + result.Add(km / SPEED_TO_KMPH); + } + break; + case SpeedUnit.Mph: + for (var mi = LOWER_MPH; mi <= UPPER_MPH; mi += MPH_STEP) { + result.Add(mi / SPEED_TO_MPH); + } + break; + case SpeedUnit.CurrentlyConfigured: + // Automatically choose from the config + return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? EnumerateSpeedLimits(SpeedUnit.Mph) + : EnumerateSpeedLimits(SpeedUnit.Kmph); + } + + return result; + } + + public static string ToMphPreciseString(float speed) { + if (IsZero(speed)) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return ToMphPrecise(speed) + " mph"; + } + + public static string ToKmphPreciseString(float speed) { + if (IsZero(speed)) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return ToKmphPrecise(speed) + " km/h"; + } + + public static bool NearlyEqual(float a, float b) { + return Mathf.Abs(a - b) < 0.001f; + } + + public static bool IsZero(float speed) { + return speed < 0.001f; + } + + public static bool IsValidRange(float speed) { + return speed >= LOWER_SPEED && speed <= UPPER_SPEED; + } + + /// + /// Convert float game speed to mph and round to nearest STEP + /// + /// + /// + public static ushort ToMphRounded(float speed) { + var mph = speed * SPEED_TO_MPH; + return (ushort)(Mathf.Round(mph / MPH_STEP) * MPH_STEP); + } + + public static ushort ToMphPrecise(float speed) { + return (ushort)Mathf.Round(speed * SPEED_TO_MPH); + } + + /// + /// Convert float game speed to km/h and round to nearest STEP + /// + /// + /// + public static ushort ToKmphRounded(float speed) { + var kmph = speed * SPEED_TO_KMPH; + return (ushort)(Mathf.Round(kmph / KMPH_STEP) * KMPH_STEP); + } + + public static ushort ToKmphPrecise(float speed) { + return (ushort)Mathf.Round(speed * SPEED_TO_KMPH); + } + + /// + /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and + /// then decrease by STEP. + /// + /// Ingame speed + /// Ingame speed decreased by the increment for MPH or KMPH + public static float GetPrevious(float speed) { + if (speed < 0f) { + return -1f; + } + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { + var rounded = ToMphRounded(speed); + return (rounded > LOWER_MPH ? rounded - MPH_STEP : LOWER_MPH) / SPEED_TO_MPH; + } else { + var rounded = ToKmphRounded(speed); + return (rounded > LOWER_KMPH ? rounded - KMPH_STEP : LOWER_KMPH) / SPEED_TO_KMPH; + } + } + + /// + /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and + /// then increase by STEP. + /// + /// Ingame speed + /// Ingame speed increased by the increment for MPH or KMPH + public static float GetNext(float speed) { + if (speed < 0f) { + return -1f; + } + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { + var rounded = ToMphRounded(speed); + return (rounded < UPPER_MPH ? rounded + MPH_STEP : UPPER_MPH) / SPEED_TO_MPH; + } + else { + var rounded = ToKmphRounded(speed); + return (rounded < UPPER_KMPH ? rounded + KMPH_STEP : UPPER_KMPH) / SPEED_TO_KMPH; + } + } + + } +} \ No newline at end of file diff --git a/TLM/TLM/Traffic/Data/SpeedLimitDef.cs b/TLM/TLM/Traffic/Data/SpeedLimitDef.cs deleted file mode 100644 index 133f6aad3..000000000 --- a/TLM/TLM/Traffic/Data/SpeedLimitDef.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; -using TrafficManager.State; -using TrafficManager.UI; -using UnityEngine; - -namespace TrafficManager.Traffic.Data { - /// - /// Defines a speed limit value with default Kmph and display value of Mph - /// for when the option is set to display Mph. The engine still uses kmph. - /// - public struct SpeedLimitDef { - public readonly ushort Kmph; - public readonly ushort Mph; - - public static SpeedLimitDef Km10 = new SpeedLimitDef(10, 5); - public static SpeedLimitDef Km20 = new SpeedLimitDef(20, 12); - public static SpeedLimitDef Km30 = new SpeedLimitDef(30, 20); - public static SpeedLimitDef Km40 = new SpeedLimitDef(40, 25); - public static SpeedLimitDef Km50 = new SpeedLimitDef(50, 30); - public static SpeedLimitDef Km60 = new SpeedLimitDef(60, 40); - public static SpeedLimitDef Km70 = new SpeedLimitDef(70, 45); - public static SpeedLimitDef Km80 = new SpeedLimitDef(80, 50); - public static SpeedLimitDef Km90 = new SpeedLimitDef(90, 55); - public static SpeedLimitDef Km100 = new SpeedLimitDef(100, 60); - public static SpeedLimitDef Km110 = new SpeedLimitDef(110, 70); - public static SpeedLimitDef Km120 = new SpeedLimitDef(120, 75); - public static SpeedLimitDef Km130 = new SpeedLimitDef(130, 80); - public static SpeedLimitDef NoLimit = new SpeedLimitDef(0, 0); - - public static Dictionary KmphToSpeedLimitDef = - new Dictionary { { 10, Km10 }, { 20, Km20 }, { 30, Km30 }, { 40, Km40 }, { 50, Km50 }, - { 60, Km60 }, { 70, Km70 }, { 80, Km80 }, { 90, Km90 }, { 100, Km100 }, - { 110, Km110 }, { 120, Km120 }, { 130, Km130 }, { 0, NoLimit } }; - - public SpeedLimitDef(ushort kmph, ushort mph) { - Kmph = kmph; - Mph = mph; - } - - public static bool DoesListContainKmph(List lst, ushort kmph) { - foreach (var v in lst) { - if (v.Kmph == kmph) { - return true; - } - } - - return false; - } - - public static int FindKmphInList(List lst, ushort kmph) { - var index = 0; - foreach (var v in lst) { - if (v.Kmph == kmph) { - return index; - } - - index++; - } - - return -1; - } - - public string ToMphString() { - if (Mph == 0) { - return Translation.GetString("Speed_limit_unlimited"); - } - - return Mph + " mph"; - } - - public string ToKmphString() { - if (Kmph == 0) { - return Translation.GetString("Speed_limit_unlimited"); - } - - return Kmph + " km/h"; - } - } -} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 538f9362d..0e28954f0 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -23,17 +23,23 @@ namespace TrafficManager.UI.SubTools { public class SpeedLimitsTool : SubTool { private bool _cursorInSecondaryPanel; - private int curSpeedLimitIndex = 0; + + /// Currently selected speed limit on the limits palette + private float currentPaletteSpeedLimit = -1f; + private bool overlayHandleHovered; private Dictionary> segmentCenterByDir = new Dictionary>(); private readonly float speedLimitSignSize = 80f; - private readonly int guiSpeedSignSize = 100; + + /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH + private readonly int guiSpeedSignSize = 90; + private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 7 * 105, 225)); private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 280, 400, 400)); private HashSet currentlyVisibleSegmentIds; private bool defaultsWindowVisible = false; private int currentInfoIndex = -1; - private int currentSpeedLimitIndex = -1; + private float currentSpeedLimit = -1f; private Texture2D RoadTexture { get { if (roadTexture == null) { @@ -64,11 +70,21 @@ public override void OnPrimaryClickOverlay() { public override void OnToolGUI(Event e) { base.OnToolGUI(e); - windowRect = GUILayout.Window(254, windowRect, _guiSpeedLimitsWindow, Translation.GetString("Speed_limits"), WindowStyle); + var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? Translation.GetString("Miles_per_hour") + : Translation.GetString("Kilometers_per_hour")) + ")"; + windowRect = GUILayout.Window(254, windowRect, _guiSpeedLimitsWindow, + Translation.GetString("Speed_limits") + unitTitle, + WindowStyle); if (defaultsWindowVisible) { - defaultsWindowRect = GUILayout.Window(258, defaultsWindowRect, _guiDefaultsWindow, Translation.GetString("Default_speed_limits"), WindowStyle); + defaultsWindowRect = GUILayout.Window( + 258, defaultsWindowRect, _guiDefaultsWindow, + Translation.GetString("Default_speed_limits"), + WindowStyle); } - _cursorInSecondaryPanel = windowRect.Contains(Event.current.mousePosition) || (defaultsWindowVisible && defaultsWindowRect.Contains(Event.current.mousePosition)); + _cursorInSecondaryPanel = windowRect.Contains(Event.current.mousePosition) + || (defaultsWindowVisible + && defaultsWindowRect.Contains(Event.current.mousePosition)); //overlayHandleHovered = false; //ShowSigns(false); @@ -92,7 +108,7 @@ public override void Cleanup() { lastCamPos = null; lastCamRot = null; currentInfoIndex = -1; - currentSpeedLimitIndex = -1; + currentSpeedLimit = -1f; } private Quaternion? lastCamRot = null; @@ -154,8 +170,12 @@ private void ShowSigns(bool viewOnly) { overlayHandleHovered = handleHovered; } + /// + /// The window for setting the defaullt speeds per road type + /// + /// private void _guiDefaultsWindow(int num) { - List mainNetInfos = SpeedLimitManager.Instance.GetCustomizableNetInfos(); + var mainNetInfos = SpeedLimitManager.Instance.GetCustomizableNetInfos(); if (mainNetInfos == null || mainNetInfos.Count <= 0) { Log._Debug($"mainNetInfos={mainNetInfos?.Count}"); @@ -174,17 +194,19 @@ private void _guiDefaultsWindow(int num) { if (updateRoadTex) UpdateRoadTex(info); - if (currentSpeedLimitIndex < 0) { - currentSpeedLimitIndex = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimitIndex(info); - Log._Debug($"set currentSpeedLimitIndex to {currentSpeedLimitIndex}"); + if (currentSpeedLimit < 0f) { + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); + Log._Debug($"set currentSpeedLimit to {currentSpeedLimit}"); } //Log._Debug($"currentInfoIndex={currentInfoIndex} currentSpeedLimitIndex={currentSpeedLimitIndex}"); + // Close button. TODO: Make more visible or obey 'Esc' pressed or something GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); - if (GUILayout.Button("X", GUILayout.Width(25))) { + if (GUILayout.Button("X", GUILayout.Width(50))) { defaultsWindowVisible = false; } + GUILayout.EndHorizontal(); // Road type label @@ -195,15 +217,17 @@ private void _guiDefaultsWindow(int num) { // switch between NetInfos GUILayout.BeginHorizontal(); - + GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); if (GUILayout.Button("←", GUILayout.Width(50))) { - currentInfoIndex = (currentInfoIndex + mainNetInfos.Count - 1) % mainNetInfos.Count; + currentInfoIndex = + (currentInfoIndex + mainNetInfos.Count - 1) % mainNetInfos.Count; info = mainNetInfos[currentInfoIndex]; - currentSpeedLimitIndex = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimitIndex(info); + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); UpdateRoadTex(info); } + GUILayout.FlexibleSpace(); GUILayout.EndVertical(); @@ -223,16 +247,16 @@ private void _guiDefaultsWindow(int num) { if (GUILayout.Button("→", GUILayout.Width(50))) { currentInfoIndex = (currentInfoIndex + 1) % mainNetInfos.Count; info = mainNetInfos[currentInfoIndex]; - currentSpeedLimitIndex = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimitIndex(info); + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); UpdateRoadTex(info); } + GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.EndHorizontal(); - GUIStyle centeredTextStyle = new GUIStyle("label"); - centeredTextStyle.alignment = TextAnchor.MiddleCenter; + var centeredTextStyle = new GUIStyle("label") { alignment = TextAnchor.MiddleCenter }; // NetInfo name GUILayout.Label(info.name, centeredTextStyle); @@ -249,8 +273,10 @@ private void _guiDefaultsWindow(int num) { GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); if (GUILayout.Button("←", GUILayout.Width(50))) { - currentSpeedLimitIndex = (currentSpeedLimitIndex + SpeedLimitManager.Instance.AvailableSpeedLimits.Count - 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + // currentSpeedLimit = (currentSpeedLimitIndex + SpeedLimitManager.Instance.AvailableSpeedLimits.Count - 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + currentSpeedLimit = SpeedLimit.GetPrevious(currentSpeedLimit); } + GUILayout.FlexibleSpace(); GUILayout.EndVertical(); @@ -260,8 +286,7 @@ private void _guiDefaultsWindow(int num) { GUILayout.FlexibleSpace(); // speed limit sign - var limit = SpeedLimitManager.Instance.AvailableSpeedLimits[currentSpeedLimitIndex]; - GUILayout.Box(TextureResources.GetSpeedLimitTexture(limit), + GUILayout.Box(TextureResources.GetSpeedLimitTexture(currentSpeedLimit), GUILayout.Width(guiSpeedSignSize), GUILayout.Height(guiSpeedSignSize)); GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph @@ -276,8 +301,10 @@ private void _guiDefaultsWindow(int num) { GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); if (GUILayout.Button("→", GUILayout.Width(50))) { - currentSpeedLimitIndex = (currentSpeedLimitIndex + 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + // currentSpeedLimitIndex = (currentSpeedLimitIndex + 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + currentSpeedLimit = SpeedLimit.GetNext(currentSpeedLimit); } + GUILayout.FlexibleSpace(); GUILayout.EndVertical(); @@ -291,13 +318,17 @@ private void _guiDefaultsWindow(int num) { GUILayout.FlexibleSpace(); if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { SpeedLimitManager.Instance.FixCurrentSpeedLimits(info); - SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimitIndex(info, currentSpeedLimitIndex); + SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); } + GUILayout.FlexibleSpace(); - if (GUILayout.Button(Translation.GetString("Save") + " & " + Translation.GetString("Apply"), GUILayout.Width(160))) { - SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimitIndex(info, currentSpeedLimitIndex); + if (GUILayout.Button( + Translation.GetString("Save") + " & " + Translation.GetString("Apply"), + GUILayout.Width(160))) { + SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); SpeedLimitManager.Instance.ClearCurrentSpeedLimits(info); } + GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); @@ -329,34 +360,48 @@ private void UpdateRoadTex(NetInfo info) { roadTexture = TextureResources.NoImageTexture2D; } + /// + /// The window for selecting and applying a speed limit + /// + /// private void _guiSpeedLimitsWindow(int num) { GUILayout.BeginHorizontal(); - Color oldColor = GUI.color; - for (int i = 0; i < SpeedLimitManager.Instance.AvailableSpeedLimits.Count; ++i) { - if (curSpeedLimitIndex != i) { + var oldColor = GUI.color; + var allSpeedLimits = SpeedLimit.EnumerateSpeedLimits(SpeedUnit.CurrentlyConfigured); + allSpeedLimits.Add(0); // add last item: no limit + + var showMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + var column = 0u; // break palette to a new line at 8 or something + var breakColumn = showMph ? 8 : 7; // MPH have more items so break 1 column later + + foreach (var speedLimit in allSpeedLimits) { + // Highlight palette item if it is very close to its float speed + if (SpeedLimit.NearlyEqual(currentPaletteSpeedLimit, speedLimit)) { GUI.color = Color.gray; } // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added GUILayout.BeginVertical(); var signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); - var limit = SpeedLimitManager.Instance.AvailableSpeedLimits[i]; if (GUILayout.Button( - TextureResources.GetSpeedLimitTexture(limit), + TextureResources.GetSpeedLimitTexture(speedLimit), GUILayout.Width(signSize), GUILayout.Height(signSize))) { - curSpeedLimitIndex = i; + currentPaletteSpeedLimit = speedLimit; } // For MPH setting display KM/H below, for KM/H setting display MPH - GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? limit.ToKmphString() : limit.ToMphString()); + GUILayout.Label(showMph ? SpeedLimit.ToKmphPreciseString(speedLimit) + : SpeedLimit.ToMphPreciseString(speedLimit)); GUILayout.EndVertical(); GUI.color = oldColor; - if (i == 6) { + // TODO: This can be calculated from SpeedLimit MPH or KMPH limit constants + if (column == breakColumn) { GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); } + column++; } GUILayout.EndHorizontal(); @@ -372,14 +417,15 @@ private void _guiSpeedLimitsWindow(int num) { } private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { - if (viewOnly && !Options.speedLimitsOverlay) + if (viewOnly && !Options.speedLimitsOverlay) { return false; + } - Vector3 center = segment.m_bounds.center; - NetManager netManager = Singleton.instance; + var center = segment.m_bounds.center; + var netManager = Singleton.instance; - bool hovered = false; - ushort speedLimitToSet = viewOnly ? (ushort)0 : SpeedLimitManager.Instance.AvailableSpeedLimits[curSpeedLimitIndex].Kmph; + var hovered = false; + var speedLimitToSet = viewOnly ? -1f : currentPaletteSpeedLimit; bool showPerLane = showLimitsPerLane; if (!viewOnly) { @@ -388,25 +434,27 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo if (showPerLane) { // show individual speed limit handle per lane int numDirections; - int numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, SpeedLimitManager.VEHICLE_TYPES); + var numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, SpeedLimitManager.VEHICLE_TYPES); - NetInfo segmentInfo = segment.Info; - Vector3 yu = (segment.m_endDirection - segment.m_startDirection).normalized; - Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; + var segmentInfo = segment.Info; + var yu = (segment.m_endDirection - segment.m_startDirection).normalized; + var xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) { xu = -xu; }*/ - float f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates - Vector3 zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu; + var f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates + var zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu; uint x = 0; var guiColor = GUI.color; - IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(segmentId, ref segment, null, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES); - bool onlyMonorailLanes = sortedLanes.Count > 0; + var sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes( + segmentId, ref segment, null, SpeedLimitManager.LANE_TYPES, + SpeedLimitManager.VEHICLE_TYPES); + var onlyMonorailLanes = sortedLanes.Count > 0; if (!viewOnly) { foreach (LanePos laneData in sortedLanes) { - byte laneIndex = laneData.laneIndex; - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + var laneIndex = laneData.laneIndex; + var laneInfo = segmentInfo.m_lanes[laneIndex]; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None) { onlyMonorailLanes = false; @@ -415,8 +463,8 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo } } - HashSet directions = new HashSet(); - int sortedLaneIndex = -1; + var directions = new HashSet(); + var sortedLaneIndex = -1; foreach (LanePos laneData in sortedLanes) { ++sortedLaneIndex; uint laneId = laneData.laneId; @@ -429,16 +477,22 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo directions.Add(laneInfo.m_finalDirection); } - var limit = SpeedLimitDef.KmphToSpeedLimitDef[ - SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId)]; - bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture( - TextureResources.GetSpeedLimitTexture(limit), - camPos, zero, f, xu, yu, x, 0, speedLimitSignSize, !viewOnly); - if (!viewOnly && !onlyMonorailLanes && (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { - MainTool.DrawStaticSquareOverlayGridTexture(TextureResources.VehicleInfoSignTextures[ExtVehicleType.PassengerTrain], camPos, zero, f, xu, yu, x, 1, speedLimitSignSize); + var laneSpeedLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId); + var hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture( + TextureResources.GetSpeedLimitTexture(laneSpeedLimit), + camPos, zero, f, xu, yu, x, 0, speedLimitSignSize, + !viewOnly); + + if (!viewOnly + && !onlyMonorailLanes + && (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { + MainTool.DrawStaticSquareOverlayGridTexture( + TextureResources.VehicleInfoSignTextures[ExtVehicleType.PassengerTrain], + camPos, zero, f, xu, yu, x, 1, speedLimitSignSize); } - if (hoveredHandle) + if (hoveredHandle) { hovered = true; + } if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { SpeedLimitManager.Instance.SetSpeedLimit(segmentId, laneIndex, laneInfo, laneId, speedLimitToSet); @@ -476,17 +530,19 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo } foreach (KeyValuePair e in segCenter) { - Vector3 screenPos; - bool visible = MainTool.WorldToScreenPoint(e.Value, out screenPos); + var visible = MainTool.WorldToScreenPoint(e.Value, out var screenPos); - if (!visible) + if (!visible) { continue; + } - float zoom = 1.0f / (e.Value - camPos).magnitude * 100f * MainTool.GetBaseZoom(); - float size = (viewOnly ? 0.8f : 1f) * speedLimitSignSize * zoom; - Color guiColor = GUI.color; - Rect boundingBox = new Rect(screenPos.x - size / 2, screenPos.y - size / 2, size, size); - bool hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); + var zoom = 1.0f / (e.Value - camPos).magnitude * 100f * MainTool.GetBaseZoom(); + var size = (viewOnly ? 0.8f : 1f) * speedLimitSignSize * zoom; + var guiColor = GUI.color; + var boundingBox = new Rect(screenPos.x - (size / 2), + screenPos.y - (size / 2), + size, size); + var hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); if (hoveredHandle) { @@ -494,15 +550,16 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo hovered = true; } + // Draw something right here, the road sign texture GUI.color = guiColor; - var limit = SpeedLimitDef.KmphToSpeedLimitDef[SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key)]; - GUI.DrawTexture(boundingBox, - TextureResources.GetSpeedLimitTexture(limit)); + var displayLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key); + var tex = TextureResources.GetSpeedLimitTexture(displayLimit); + GUI.DrawTexture(boundingBox, tex); - if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { + if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { // change the speed limit to the selected one //Log._Debug($"Setting speed limit of segment {segmentId}, dir {e.Key.ToString()} to {speedLimitToSet}"); - SpeedLimitManager.Instance.SetSpeedLimit(segmentId, e.Key, speedLimitToSet); + SpeedLimitManager.Instance.SetSpeedLimit(segmentId, e.Key, currentPaletteSpeedLimit); if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index 4716af9a2..ca0729759 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -47,7 +47,7 @@ public class TextureResources { public static readonly Texture2D ClockPlayTexture2D; public static readonly Texture2D ClockPauseTexture2D; public static readonly Texture2D ClockTestTexture2D; - public static readonly IDictionary SpeedLimitTextures; + public static readonly IDictionary SpeedLimitTextures; public static readonly IDictionary> VehicleRestrictionTextures; public static readonly IDictionary VehicleInfoSignTextures; public static readonly IDictionary ParkingRestrictionTextures; @@ -69,8 +69,7 @@ public class TextureResources { public static readonly Texture2D RemoveButtonTexture2D; public static readonly Texture2D WindowBackgroundTexture2D; - static TextureResources() - { + static TextureResources() { // missing image NoImageTexture2D = LoadDllResource("noimage.png", 64, 64); @@ -112,8 +111,10 @@ static TextureResources() PedestrianRedLightTexture2D = LoadDllResource("pedestrian_light_1.png", 73, 123); PedestrianGreenLightTexture2D = LoadDllResource("pedestrian_light_2.png", 73, 123); // light mode - LightModeTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_mode.png"), 103, 95); - LightCounterTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_counter.png"), 103, 95); + LightModeTexture2D = + LoadDllResource(Translation.GetTranslatedFileName("light_mode.png"), 103, 95); + LightCounterTexture2D = + LoadDllResource(Translation.GetTranslatedFileName("light_counter.png"), 103, 95); // pedestrian mode PedestrianModeAutomaticTexture2D = LoadDllResource("pedestrian_mode_1.png", 73, 70); PedestrianModeManualTexture2D = LoadDllResource("pedestrian_mode_2.png", 73, 73); @@ -124,7 +125,7 @@ static TextureResources() PrioritySignTextures[PriorityType.Main] = LoadDllResource("sign_priority.png", 200, 200); PrioritySignTextures[PriorityType.Stop] = LoadDllResource("sign_stop.png", 200, 200); PrioritySignTextures[PriorityType.Yield] = LoadDllResource("sign_yield.png", 200, 200); - + // delete priority sign SignRemoveTexture2D = LoadDllResource("remove_signs.png", 256, 256); @@ -133,19 +134,15 @@ static TextureResources() ClockPauseTexture2D = LoadDllResource("clock_pause.png", 512, 512); ClockTestTexture2D = LoadDllResource("clock_test.png", 512, 512); - SpeedLimitTextures = new TinyDictionary(); - // Load speed limit signs for Kmph and Mph - foreach (var speedLimit in SpeedLimitManager.Instance.AvailableSpeedLimits) { - if (!SpeedLimitTextures.ContainsKey(speedLimit.Kmph)) { - SpeedLimitTextures.Add(speedLimit.Kmph, - LoadDllResource(speedLimit.Kmph + ".png", 200, 200)); - } + // TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor + SpeedLimitTextures = new TinyDictionary(); - if (!SpeedLimitTextures.ContainsKey(speedLimit.Mph)) { - var resource = LoadDllResource(speedLimit.Mph + ".png", 200, 200); - SpeedLimitTextures.Add(speedLimit.Mph, - resource != null ? resource : SpeedLimitTextures[10]); - } + // Load shared speed limit signs for Kmph and Mph + // Assumes that signs from 0 to 140 with step 5 exist, 0.png denotes no limit sign + for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) { + var resource = LoadDllResource(speedLimit + ".png", 200, 200); + SpeedLimitTextures.Add(speedLimit, + resource ?? SpeedLimitTextures[5]); } VehicleRestrictionTextures = new TinyDictionary>(); @@ -154,14 +151,18 @@ static TextureResources() VehicleRestrictionTextures[ExtVehicleType.CargoTruck] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.Emergency] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.PassengerCar] = new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.PassengerTrain] = new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.PassengerTrain] = + new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.Service] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.Taxi] = new TinyDictionary(); - foreach (KeyValuePair> e in VehicleRestrictionTextures) { - foreach (bool b in new bool[]{false, true}) { + foreach (KeyValuePair> e in + VehicleRestrictionTextures) { + foreach (bool b in new bool[] {false, true}) { string suffix = b ? "allowed" : "forbidden"; - e.Value[b] = LoadDllResource(e.Key.ToString().ToLower() + "_" + suffix + ".png", 200, 200); + e.Value[b] = + LoadDllResource(e.Key.ToString().ToLower() + "_" + suffix + ".png", 200, + 200); } } @@ -181,21 +182,30 @@ static TextureResources() LeftOnRedForbiddenTexture2D = LoadDllResource("left_on_red_forbidden.png", 200, 200); EnterBlockedJunctionAllowedTexture2D = LoadDllResource("enterblocked_allowed.png", 200, 200); - EnterBlockedJunctionForbiddenTexture2D = LoadDllResource("enterblocked_forbidden.png", 200, 200); + EnterBlockedJunctionForbiddenTexture2D = + LoadDllResource("enterblocked_forbidden.png", 200, 200); PedestrianCrossingAllowedTexture2D = LoadDllResource("crossing_allowed.png", 200, 200); PedestrianCrossingForbiddenTexture2D = LoadDllResource("crossing_forbidden.png", 200, 200); VehicleInfoSignTextures = new TinyDictionary(); - VehicleInfoSignTextures[ExtVehicleType.Bicycle] = LoadDllResource("bicycle_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.Bicycle] = + LoadDllResource("bicycle_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.Bus] = LoadDllResource("bus_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.CargoTrain] = LoadDllResource("cargotrain_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.CargoTruck] = LoadDllResource("cargotruck_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.Emergency] = LoadDllResource("emergency_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.PassengerCar] = LoadDllResource("passengercar_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.PassengerTrain] = LoadDllResource("passengertrain_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.RailVehicle] = VehicleInfoSignTextures[ExtVehicleType.PassengerTrain]; - VehicleInfoSignTextures[ExtVehicleType.Service] = LoadDllResource("service_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.CargoTrain] = + LoadDllResource("cargotrain_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.CargoTruck] = + LoadDllResource("cargotruck_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.Emergency] = + LoadDllResource("emergency_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.PassengerCar] = + LoadDllResource("passengercar_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.PassengerTrain] = + LoadDllResource("passengertrain_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.RailVehicle] = + VehicleInfoSignTextures[ExtVehicleType.PassengerTrain]; + VehicleInfoSignTextures[ExtVehicleType.Service] = + LoadDllResource("service_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.Taxi] = LoadDllResource("taxi_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.Tram] = LoadDllResource("tram_infosign.png", 449, 411); @@ -204,8 +214,22 @@ static TextureResources() WindowBackgroundTexture2D = LoadDllResource("WindowBackground.png", 16, 60); } - public static Texture2D GetSpeedLimitTexture(SpeedLimitDef limit) { - return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? SpeedLimitTextures[limit.Mph] : SpeedLimitTextures[limit.Kmph]; + /// + /// Given speed limit, round it up to nearest Kmph or Mph and produce a texture + /// + /// Ingame speed + /// The texture, hopefully it existed + public static Texture2D GetSpeedLimitTexture(float speedLimit) { + if (speedLimit >= SpeedLimitManager.MAX_SPEED) { + // Special value for max speed + return SpeedLimitTextures[0]; + } + var index = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? SpeedLimit.ToMphRounded(speedLimit) + : SpeedLimit.ToKmphRounded(speedLimit); + var trimIndex = Math.Min(SpeedLimit.UPPER_KMPH, Math.Max((ushort)0, index)); + // Log._Debug($"texture for {speedLimit} is {index} trim={trimIndex}"); + return SpeedLimitTextures[trimIndex]; } private static Texture2D LoadDllResource(string resourceName, int width, int height) diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 524b8d9ae..46cc771e8 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -948,7 +948,7 @@ private void _guiVehicles() { ExtCitizenInstance driverInst = ExtCitizenInstanceManager.Instance.ExtInstances[CustomPassengerCarAI.GetDriverInstanceId((ushort)i, ref Singleton.instance.m_vehicles.m_buffer[i])]; bool startNode = vState.currentStartNode; ushort segmentId = vState.currentSegmentId; - ushort vehSpeed = SpeedLimitManager.Instance.VehicleToCustomSpeed(vehicle.GetLastFrameVelocity().magnitude); + float vehSpeed = SpeedLimit.ToKmphRounded(vehicle.GetLastFrameVelocity().magnitude); #if DEBUG if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { @@ -956,8 +956,19 @@ private void _guiVehicles() { } #endif - String labelStr = "V #" + i + " is a " + (vState.recklessDriver ? "reckless " : "") + vState.flags + " " + vState.vehicleType + " @ ~" + vehSpeed + " km/h [^2=" + vState.SqrVelocity + "] (len: " + vState.totalLength + ", " + vState.JunctionTransitState + " @ " + vState.currentSegmentId + " (" + vState.currentStartNode + "), l. " + vState.currentLaneIndex + " -> " + vState.nextSegmentId + ", l. " + vState.nextLaneIndex + "), w: " + vState.waitTime + "\n" + - "di: " + driverInst.instanceId + " dc: " + driverInst.GetCitizenId() + " m: " + driverInst.pathMode.ToString() + " f: " + driverInst.failedParkingAttempts + " l: " + driverInst.parkingSpaceLocation + " lid: " + driverInst.parkingSpaceLocationId + " ltsu: " + vState.lastTransitStateUpdate + " lpu: " + vState.lastPositionUpdate + " als: " + vState.lastAltLaneSelSegmentId + " srnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetStaticVehicleRand((ushort)i) + " trnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetTimedVehicleRand((ushort)i); + string labelStr = "V #" + i + " is a " + (vState.recklessDriver ? "reckless " : string.Empty) + + vState.flags + " " + vState.vehicleType + " @ ~" + vehSpeed + " km/h [^2=" + + vState.SqrVelocity + "] (len: " + vState.totalLength + ", " + + vState.JunctionTransitState + " @ " + vState.currentSegmentId + + " (" + vState.currentStartNode + "), l. " + vState.currentLaneIndex + + " -> " + vState.nextSegmentId + ", l. " + vState.nextLaneIndex + "), w: " + + vState.waitTime + "\n" + "di: " + driverInst.instanceId + + " dc: " + driverInst.GetCitizenId() + " m: " + driverInst.pathMode + + " f: " + driverInst.failedParkingAttempts + " l: " + driverInst.parkingSpaceLocation + + " lid: " + driverInst.parkingSpaceLocationId + " ltsu: " + vState.lastTransitStateUpdate + + " lpu: " + vState.lastPositionUpdate + " als: " + vState.lastAltLaneSelSegmentId + + " srnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetStaticVehicleRand((ushort)i) + + " trnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetTimedVehicleRand((ushort)i); Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); From d6761c62bd44718f56119ccb8516b4e6e8a7438e Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Tue, 18 Jun 2019 00:25:39 +0200 Subject: [PATCH 049/142] Make CI happy --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 6 ++++-- TLM/TLM/State/Flags.cs | 3 ++- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index b3b34c7f2..c77a71da5 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -369,7 +369,8 @@ public float GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = } string infoName = info.name; - if (!vanillaLaneSpeedLimitsByNetInfoName.TryGetValue(infoName, out var vanillaSpeedLimits)) { + float[] vanillaSpeedLimits; + if (!vanillaLaneSpeedLimitsByNetInfoName.TryGetValue(infoName, out vanillaSpeedLimits)) { return 0; } @@ -799,7 +800,8 @@ public bool LoadData(Dictionary data) { const bool success = true; Log.Info($"Loading custom default speed limit data. {data.Count} elements"); foreach (var e in data) { - if (!NetInfoByName.TryGetValue(e.Key, out var netInfo)) { + NetInfo netInfo; + if (!NetInfoByName.TryGetValue(e.Key, out netInfo)) { continue; } diff --git a/TLM/TLM/State/Flags.cs b/TLM/TLM/State/Flags.cs index cd1ffdb5e..dfe83f7fb 100644 --- a/TLM/TLM/State/Flags.cs +++ b/TLM/TLM/State/Flags.cs @@ -759,7 +759,8 @@ internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { try { Monitor.Enter(laneSpeedLimitLock); - if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out var speedLimit)) { + float speedLimit; + if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out speedLimit)) { return null; } diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 0e28954f0..8736b115f 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -530,7 +530,8 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo } foreach (KeyValuePair e in segCenter) { - var visible = MainTool.WorldToScreenPoint(e.Value, out var screenPos); + Vector3 screenPos; + var visible = MainTool.WorldToScreenPoint(e.Value, out screenPos); if (!visible) { continue; From f30a37c05e68eaa807fbb5e3bff16f512c865604 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Tue, 18 Jun 2019 01:04:24 +0200 Subject: [PATCH 050/142] Tabified files; Fixed Shift+S/Delete shortcuts in lane connector --- TLM/TLM/State/KeyMapping.cs | 560 +++++++++--------- TLM/TLM/State/Options.cs | 26 +- TLM/TLM/UI/LinearSpriteButton.cs | 17 +- TLM/TLM/UI/MainMenu/ClearTrafficButton.cs | 27 +- TLM/TLM/UI/MainMenu/DespawnButton.cs | 27 +- .../UI/MainMenu/JunctionRestrictionsButton.cs | 14 +- TLM/TLM/UI/MainMenu/LaneArrowsButton.cs | 14 +- TLM/TLM/UI/MainMenu/LaneConnectorButton.cs | 12 +- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 64 +- .../UI/MainMenu/ManualTrafficLightsButton.cs | 10 +- TLM/TLM/UI/MainMenu/MenuToolModeButton.cs | 6 +- .../UI/MainMenu/ParkingRestrictionsButton.cs | 27 +- TLM/TLM/UI/MainMenu/PrioritySignsButton.cs | 12 +- TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs | 12 +- .../UI/MainMenu/TimedTrafficLightsButton.cs | 27 +- .../UI/MainMenu/ToggleTrafficLightsButton.cs | 12 +- .../UI/MainMenu/VehicleRestrictionsButton.cs | 27 +- TLM/TLM/UI/SubTools/LaneConnectorTool.cs | 42 +- TLM/TLM/UI/ToolMode.cs | 34 +- TLM/TLM/UI/UIMainMenuButton.cs | 27 +- 20 files changed, 463 insertions(+), 534 deletions(-) diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs index 3cd1bc725..a278e8c96 100644 --- a/TLM/TLM/State/KeyMapping.cs +++ b/TLM/TLM/State/KeyMapping.cs @@ -11,284 +11,284 @@ using UnityEngine; namespace TrafficManager.State { - public class OptionsKeymappingMain : OptionsKeymapping { - private void Awake() { - TryCreateConfig(); - AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), - KeyToggleTMPEMainMenu); - - AddKeymapping(Translation.GetString("Keybind_toggle_traffic_lights_tool"), - KeyToggleTrafficLightTool); - AddKeymapping(Translation.GetString("Keybind_use_lane_arrow_tool"), - KeyLaneArrowTool); - AddKeymapping(Translation.GetString("Keybind_use_lane_connections_tool"), - KeyLaneConnectionsTool); - AddKeymapping(Translation.GetString("Keybind_use_priority_signs_tool"), - KeyPrioritySignsTool); - AddKeymapping(Translation.GetString("Keybind_use_junction_restrictions_tool"), - KeyJunctionRestrictionsTool); - AddKeymapping(Translation.GetString("Keybind_use_speed_limits_tool"), - KeySpeedLimitsTool); - - AddKeymapping(Translation.GetString("Keybind_lane_connector_stay_in_lane"), - KeyLaneConnectorStayInLane); - } - } - - public class OptionsKeymapping : UICustomControl { - protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; - private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; - - public static SavedInputKey KeyToggleTMPEMainMenu = - new SavedInputKey("keyToggleTMPEMainMenu", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.T, true, false, true), - true); - - public static SavedInputKey KeyToggleTrafficLightTool = - new SavedInputKey("keyToggleTrafficLightTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.T, true, true, false), - true); - - public static SavedInputKey KeyLaneArrowTool = - new SavedInputKey("keyLaneArrowTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.A, true, true, false), - true); - - public static SavedInputKey KeyLaneConnectionsTool = - new SavedInputKey("keyLaneConnectionsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.C, true, true, false), - true); - - public static SavedInputKey KeyPrioritySignsTool = - new SavedInputKey("keyPrioritySignsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.P, true, true, false), - true); - - public static SavedInputKey KeyJunctionRestrictionsTool = - new SavedInputKey("keyJunctionRestrictionsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.J, true, true, false), - true); - - public static SavedInputKey KeySpeedLimitsTool = - new SavedInputKey("keySpeedLimitsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.S, true, true, false), - true); - - public static SavedInputKey KeyLaneConnectorStayInLane = - new SavedInputKey("keyLaneConnectorStayInLane", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.S, false, true, false), - true); - - private SavedInputKey editingBinding_; - private string editingBindingCategory_; - - private int count_; - - protected void TryCreateConfig() { - try { - // Creating setting file - if (GameSettings.FindSettingsFileByName(KEYBOARD_SHORTCUTS_FILENAME) == null) { - GameSettings.AddSettingsFile(new SettingsFile - {fileName = KEYBOARD_SHORTCUTS_FILENAME}); - } - } - catch (Exception) { - Log._Debug("Could not load/create the keyboard shortcuts file."); - } - } - - /// - /// Creates a row in the current panel with the label and the button which will prompt user to press a new key. - /// - /// Text to display - /// A SavedInputKey from GlobalConfig.KeyboardShortcuts - protected void AddKeymapping(string label, SavedInputKey savedInputKey) { - var uiPanel = - component.AttachUIComponent(UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as - UIPanel; - if (count_++ % 2 == 1) uiPanel.backgroundSprite = null; - - // Create a label - var uILabel = uiPanel.Find("Name"); - - // Create a button which displays the shortcut and modifies it on click - var uIButton = uiPanel.Find("Binding"); - uIButton.eventKeyDown += OnBindingKeyDown; - uIButton.eventMouseDown += OnBindingMouseDown; - - // Set label text (as provided) and set button text from the SavedInputKey - uILabel.text = label; - uIButton.text = savedInputKey.ToLocalizedString("KEYNAME"); - uIButton.objectUserData = savedInputKey; - } - - protected void OnEnable() { - LocaleManager.eventLocaleChanged += OnLocaleChanged; - } - - protected void OnDisable() { - LocaleManager.eventLocaleChanged -= OnLocaleChanged; - } - - protected void OnLocaleChanged() { - RefreshBindableInputs(); - } - - protected bool IsModifierKey(KeyCode code) { - return code == KeyCode.LeftControl || code == KeyCode.RightControl || - code == KeyCode.LeftShift || code == KeyCode.RightShift || code == KeyCode.LeftAlt || - code == KeyCode.RightAlt; - } - - protected bool IsControlDown() { - return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); - } - - protected bool IsShiftDown() { - return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - } - - protected bool IsAltDown() { - return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); - } - - protected bool IsUnbindableMouseButton(UIMouseButton code) { - return code == UIMouseButton.Left || code == UIMouseButton.Right; - } - - protected KeyCode ButtonToKeycode(UIMouseButton button) { - if (button == UIMouseButton.Left) { - return KeyCode.Mouse0; - } - - if (button == UIMouseButton.Right) { - return KeyCode.Mouse1; - } - - if (button == UIMouseButton.Middle) { - return KeyCode.Mouse2; - } - - if (button == UIMouseButton.Special0) { - return KeyCode.Mouse3; - } - - if (button == UIMouseButton.Special1) { - return KeyCode.Mouse4; - } - - if (button == UIMouseButton.Special2) { - return KeyCode.Mouse5; - } - - if (button == UIMouseButton.Special3) { - return KeyCode.Mouse6; - } - - return KeyCode.None; - } - - protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { - // This will only work if the user clicked the modify button - // otherwise no effect - if (editingBinding_ != null && !IsModifierKey(p.keycode)) { - p.Use(); - UIView.PopModal(); - var keycode = p.keycode; - var inputKey = (p.keycode == KeyCode.Escape) - ? editingBinding_.value - : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); - if (p.keycode == KeyCode.Backspace) { - inputKey = SavedInputKey.Empty; - } - - editingBinding_.value = inputKey; - var uITextComponent = p.source as UITextComponent; - uITextComponent.text = editingBinding_.ToLocalizedString("KEYNAME"); - editingBinding_ = null; - editingBindingCategory_ = string.Empty; - } - } - - protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { - // This will only work if the user is not in the process of changing the shortcut - if (editingBinding_ == null) { - p.Use(); - editingBinding_ = (SavedInputKey) p.source.objectUserData; - editingBindingCategory_ = p.source.stringUserData; - var uIButton = p.source as UIButton; - uIButton.buttonsMask = - UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | - UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | - UIMouseButton.Special3; - uIButton.text = "Press any key"; - p.source.Focus(); - UIView.PushModal(p.source); - } else if (!IsUnbindableMouseButton(p.buttons)) { - // This will work if the user clicks while the shortcut change is in progress - p.Use(); - UIView.PopModal(); - var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), - IsControlDown(), IsShiftDown(), - IsAltDown()); - - editingBinding_.value = inputKey; - var uIButton2 = p.source as UIButton; - uIButton2.text = editingBinding_.ToLocalizedString("KEYNAME"); - uIButton2.buttonsMask = UIMouseButton.Left; - editingBinding_ = null; - editingBindingCategory_ = string.Empty; - } - } - - protected void RefreshBindableInputs() { - foreach (var current in component.GetComponentsInChildren()) { - var uITextComponent = current.Find("Binding"); - if (uITextComponent != null) { - var savedInputKey = uITextComponent.objectUserData as SavedInputKey; - if (savedInputKey != null) { - uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); - } - } - - var uILabel = current.Find("Name"); - if (uILabel != null) { - uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); - } - } - } - - protected InputKey GetDefaultEntry(string entryName) { - var field = - typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); - if (field == null) { - return 0; - } - - var value = field.GetValue(null); - if (value is InputKey) { - return (InputKey) value; - } - - return 0; - } - - protected void RefreshKeyMapping() { - foreach (var current in component.GetComponentsInChildren()) { - var uITextComponent = current.Find("Binding"); - var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; - if (editingBinding_ != savedInputKey) { - uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); - } - } - } - } + public class OptionsKeymappingMain : OptionsKeymapping { + private void Awake() { + TryCreateConfig(); + AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), + KeyToggleTMPEMainMenu); + + AddKeymapping(Translation.GetString("Keybind_toggle_traffic_lights_tool"), + KeyToggleTrafficLightTool); + AddKeymapping(Translation.GetString("Keybind_use_lane_arrow_tool"), + KeyLaneArrowTool); + AddKeymapping(Translation.GetString("Keybind_use_lane_connections_tool"), + KeyLaneConnectionsTool); + AddKeymapping(Translation.GetString("Keybind_use_priority_signs_tool"), + KeyPrioritySignsTool); + AddKeymapping(Translation.GetString("Keybind_use_junction_restrictions_tool"), + KeyJunctionRestrictionsTool); + AddKeymapping(Translation.GetString("Keybind_use_speed_limits_tool"), + KeySpeedLimitsTool); + + AddKeymapping(Translation.GetString("Keybind_lane_connector_stay_in_lane"), + KeyLaneConnectorStayInLane); + } + } + + public class OptionsKeymapping : UICustomControl { + protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; + private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; + + public static SavedInputKey KeyToggleTMPEMainMenu = + new SavedInputKey("keyToggleTMPEMainMenu", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.T, true, false, true), + true); + + public static SavedInputKey KeyToggleTrafficLightTool = + new SavedInputKey("keyToggleTrafficLightTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.T, true, true, false), + true); + + public static SavedInputKey KeyLaneArrowTool = + new SavedInputKey("keyLaneArrowTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.A, true, true, false), + true); + + public static SavedInputKey KeyLaneConnectionsTool = + new SavedInputKey("keyLaneConnectionsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.C, true, true, false), + true); + + public static SavedInputKey KeyPrioritySignsTool = + new SavedInputKey("keyPrioritySignsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.P, true, true, false), + true); + + public static SavedInputKey KeyJunctionRestrictionsTool = + new SavedInputKey("keyJunctionRestrictionsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.J, true, true, false), + true); + + public static SavedInputKey KeySpeedLimitsTool = + new SavedInputKey("keySpeedLimitsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.S, true, true, false), + true); + + public static SavedInputKey KeyLaneConnectorStayInLane = + new SavedInputKey("keyLaneConnectorStayInLane", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.S, false, true, false), + true); + + private SavedInputKey editingBinding_; + private string editingBindingCategory_; + + private int count_; + + protected void TryCreateConfig() { + try { + // Creating setting file + if (GameSettings.FindSettingsFileByName(KEYBOARD_SHORTCUTS_FILENAME) == null) { + GameSettings.AddSettingsFile(new SettingsFile + {fileName = KEYBOARD_SHORTCUTS_FILENAME}); + } + } + catch (Exception) { + Log._Debug("Could not load/create the keyboard shortcuts file."); + } + } + + /// + /// Creates a row in the current panel with the label and the button which will prompt user to press a new key. + /// + /// Text to display + /// A SavedInputKey from GlobalConfig.KeyboardShortcuts + protected void AddKeymapping(string label, SavedInputKey savedInputKey) { + var uiPanel = + component.AttachUIComponent(UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as + UIPanel; + if (count_++ % 2 == 1) uiPanel.backgroundSprite = null; + + // Create a label + var uILabel = uiPanel.Find("Name"); + + // Create a button which displays the shortcut and modifies it on click + var uIButton = uiPanel.Find("Binding"); + uIButton.eventKeyDown += OnBindingKeyDown; + uIButton.eventMouseDown += OnBindingMouseDown; + + // Set label text (as provided) and set button text from the SavedInputKey + uILabel.text = label; + uIButton.text = savedInputKey.ToLocalizedString("KEYNAME"); + uIButton.objectUserData = savedInputKey; + } + + protected void OnEnable() { + LocaleManager.eventLocaleChanged += OnLocaleChanged; + } + + protected void OnDisable() { + LocaleManager.eventLocaleChanged -= OnLocaleChanged; + } + + protected void OnLocaleChanged() { + RefreshBindableInputs(); + } + + protected bool IsModifierKey(KeyCode code) { + return code == KeyCode.LeftControl || code == KeyCode.RightControl || + code == KeyCode.LeftShift || code == KeyCode.RightShift || code == KeyCode.LeftAlt || + code == KeyCode.RightAlt; + } + + protected bool IsControlDown() { + return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + } + + protected bool IsShiftDown() { + return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + } + + protected bool IsAltDown() { + return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); + } + + protected bool IsUnbindableMouseButton(UIMouseButton code) { + return code == UIMouseButton.Left || code == UIMouseButton.Right; + } + + protected KeyCode ButtonToKeycode(UIMouseButton button) { + if (button == UIMouseButton.Left) { + return KeyCode.Mouse0; + } + + if (button == UIMouseButton.Right) { + return KeyCode.Mouse1; + } + + if (button == UIMouseButton.Middle) { + return KeyCode.Mouse2; + } + + if (button == UIMouseButton.Special0) { + return KeyCode.Mouse3; + } + + if (button == UIMouseButton.Special1) { + return KeyCode.Mouse4; + } + + if (button == UIMouseButton.Special2) { + return KeyCode.Mouse5; + } + + if (button == UIMouseButton.Special3) { + return KeyCode.Mouse6; + } + + return KeyCode.None; + } + + protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { + // This will only work if the user clicked the modify button + // otherwise no effect + if (editingBinding_ != null && !IsModifierKey(p.keycode)) { + p.Use(); + UIView.PopModal(); + var keycode = p.keycode; + var inputKey = (p.keycode == KeyCode.Escape) + ? editingBinding_.value + : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); + if (p.keycode == KeyCode.Backspace) { + inputKey = SavedInputKey.Empty; + } + + editingBinding_.value = inputKey; + var uITextComponent = p.source as UITextComponent; + uITextComponent.text = editingBinding_.ToLocalizedString("KEYNAME"); + editingBinding_ = null; + editingBindingCategory_ = string.Empty; + } + } + + protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { + // This will only work if the user is not in the process of changing the shortcut + if (editingBinding_ == null) { + p.Use(); + editingBinding_ = (SavedInputKey) p.source.objectUserData; + editingBindingCategory_ = p.source.stringUserData; + var uIButton = p.source as UIButton; + uIButton.buttonsMask = + UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | + UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | + UIMouseButton.Special3; + uIButton.text = "Press any key"; + p.source.Focus(); + UIView.PushModal(p.source); + } else if (!IsUnbindableMouseButton(p.buttons)) { + // This will work if the user clicks while the shortcut change is in progress + p.Use(); + UIView.PopModal(); + var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), + IsControlDown(), IsShiftDown(), + IsAltDown()); + + editingBinding_.value = inputKey; + var uIButton2 = p.source as UIButton; + uIButton2.text = editingBinding_.ToLocalizedString("KEYNAME"); + uIButton2.buttonsMask = UIMouseButton.Left; + editingBinding_ = null; + editingBindingCategory_ = string.Empty; + } + } + + protected void RefreshBindableInputs() { + foreach (var current in component.GetComponentsInChildren()) { + var uITextComponent = current.Find("Binding"); + if (uITextComponent != null) { + var savedInputKey = uITextComponent.objectUserData as SavedInputKey; + if (savedInputKey != null) { + uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + } + } + + var uILabel = current.Find("Name"); + if (uILabel != null) { + uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); + } + } + } + + protected InputKey GetDefaultEntry(string entryName) { + var field = + typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); + if (field == null) { + return 0; + } + + var value = field.GetValue(null); + if (value is InputKey) { + return (InputKey) value; + } + + return 0; + } + + protected void RefreshKeyMapping() { + foreach (var current in component.GetComponentsInChildren()) { + var uITextComponent = current.Find("Binding"); + var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; + if (editingBinding_ != savedInputKey) { + uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + } + } + } + } } \ No newline at end of file diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index 941c7183f..7806dda9c 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -388,23 +388,23 @@ public static void makeSettings(UIHelperBase helper) { Indent(turnOnRedEnabledToggle); - // KEYBOARD - ++tabIndex; + // KEYBOARD + ++tabIndex; - AddOptionTab(tabStrip, Translation.GetString("Keybinds")); - tabStrip.selectedIndex = tabIndex; + AddOptionTab(tabStrip, Translation.GetString("Keybinds")); + tabStrip.selectedIndex = tabIndex; - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; - panelHelper = new UIHelper(currentPanel); + panelHelper = new UIHelper(currentPanel); - var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); - ((UIPanel)((UIHelper)keyboardGroup).self).gameObject.AddComponent(); + var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); + ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); #if DEBUG // GLOBAL CONFIG diff --git a/TLM/TLM/UI/LinearSpriteButton.cs b/TLM/TLM/UI/LinearSpriteButton.cs index bc323a9b0..d9ada8abb 100644 --- a/TLM/TLM/UI/LinearSpriteButton.cs +++ b/TLM/TLM/UI/LinearSpriteButton.cs @@ -95,11 +95,12 @@ public override void Start() { public abstract string Tooltip { get; } public abstract bool Visible { get; } public abstract void HandleClick(UIMouseEventParameter p); - public virtual SavedInputKey ShortcutKey { - get { return null; } - } - protected override void OnClick(UIMouseEventParameter p) { + public virtual SavedInputKey ShortcutKey { + get { return null; } + } + + protected override void OnClick(UIMouseEventParameter p) { HandleClick(p); UpdateProperties(); } @@ -114,10 +115,10 @@ internal void UpdateProperties() { m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = GetButtonForegroundTextureId(ButtonName, FunctionName, active); m_ForegroundSprites.m_Hovered = m_PressedFgSprite = GetButtonForegroundTextureId(ButtonName, FunctionName, true); - var shortcutText = ShortcutKey == null - ? string.Empty - : "\n" + ShortcutKey.ToLocalizedString("KEYNAME"); - tooltip = Translation.GetString(Tooltip) + shortcutText; + var shortcutText = ShortcutKey == null + ? string.Empty + : "\n" + ShortcutKey.ToLocalizedString("KEYNAME"); + tooltip = Translation.GetString(Tooltip) + shortcutText; isVisible = Visible; this.Invalidate(); diff --git a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs index 8e8876679..8b498ce9e 100644 --- a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs +++ b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs @@ -8,29 +8,10 @@ namespace TrafficManager.UI.MainMenu { public class ClearTrafficButton : MenuButton { - public override bool Active { - get { - return false; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.ClearTraffic; - } - } - - public override string Tooltip { - get { - return "Clear_Traffic"; - } - } - - public override bool Visible { - get { - return true; - } - } + public override bool Active => false; + public override ButtonFunction Function => ButtonFunction.ClearTraffic; + public override string Tooltip => "Clear_Traffic"; + public override bool Visible => true; public override void OnClickInternal(UIMouseEventParameter p) { ConfirmPanel.ShowModal(Translation.GetString("Clear_Traffic"), Translation.GetString("Clear_Traffic") + "?", delegate (UIComponent comp, int ret) { diff --git a/TLM/TLM/UI/MainMenu/DespawnButton.cs b/TLM/TLM/UI/MainMenu/DespawnButton.cs index 713782812..b72f7d272 100644 --- a/TLM/TLM/UI/MainMenu/DespawnButton.cs +++ b/TLM/TLM/UI/MainMenu/DespawnButton.cs @@ -8,29 +8,10 @@ namespace TrafficManager.UI.MainMenu { public class DespawnButton : MenuButton { - public override bool Active { - get { - return false; - } - } - - public override ButtonFunction Function { - get { - return Options.disableDespawning ? ButtonFunction.DespawnDisabled : ButtonFunction.DespawnEnabled; - } - } - - public override string Tooltip { - get { - return Options.disableDespawning ? "Enable_despawning" : "Disable_despawning"; - } - } - - public override bool Visible { - get { - return true; - } - } + public override bool Active => false; + public override ButtonFunction Function => Options.disableDespawning ? ButtonFunction.DespawnDisabled : ButtonFunction.DespawnEnabled; + public override string Tooltip => Options.disableDespawning ? "Enable_despawning" : "Disable_despawning"; + public override bool Visible => true; public override void OnClickInternal(UIMouseEventParameter p) { UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs index 230e9c624..29c6600ff 100644 --- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class JunctionRestrictionsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.JunctionRestrictions; - public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; - public override string Tooltip => "Junction_restrictions"; - public override bool Visible => Options.junctionRestrictionsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyJunctionRestrictionsTool; - } + public class JunctionRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.JunctionRestrictions; + public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; + public override string Tooltip => "Junction_restrictions"; + public override bool Visible => Options.junctionRestrictionsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyJunctionRestrictionsTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs index f2f3a3ba0..88e431483 100644 --- a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class LaneArrowsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.LaneChange; - public override ButtonFunction Function => ButtonFunction.LaneArrows; - public override string Tooltip => "Change_lane_arrows"; - public override bool Visible => true; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneArrowTool; - } + public class LaneArrowsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.LaneChange; + public override ButtonFunction Function => ButtonFunction.LaneArrows; + public override string Tooltip => "Change_lane_arrows"; + public override bool Visible => true; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneArrowTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs index cfcd2c9f3..71a32232d 100644 --- a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs @@ -4,9 +4,9 @@ namespace TrafficManager.UI.MainMenu { public class LaneConnectorButton : MenuToolModeButton { public override ToolMode ToolMode => ToolMode.LaneConnector; - public override ButtonFunction Function => ButtonFunction.LaneConnector; - public override string Tooltip => "Lane_connector"; - public override bool Visible => Options.laneConnectorEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneConnectionsTool; - } -} + public override ButtonFunction Function => ButtonFunction.LaneConnector; + public override string Tooltip => "Lane_connector"; + public override bool Visible => Options.laneConnectorEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneConnectionsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index 307c8b3da..1222c17bc 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -208,36 +208,36 @@ public void UpdatePosition(Vector2 pos) { Invalidate(); } - public void OnGUI() { - // Some safety checks to not trigger while full screen/modals are open - // Check the key and then click the corresponding button - if (!UIView.HasModalInput() - && !UIView.HasInputFocus()) { - if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { - ClickToolButton(typeof(ToggleTrafficLightsButton)); - } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { - ClickToolButton(typeof(LaneArrowsButton)); - } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(LaneConnectorButton)); - } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(PrioritySignsButton)); - } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(JunctionRestrictionsButton)); - } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(SpeedLimitsButton)); - } - } - } - - /// For given button class type, find it in the tool palette and send click - /// Something like typeof(ToggleTrafficLightsButton) - void ClickToolButton(Type t) { - for (var i = 0; i < MENU_BUTTON_TYPES.Length; i++) { - if (MENU_BUTTON_TYPES[i] == t) { - Buttons[i].SimulateClick(); - return; - } - } - } - } + public void OnGUI() { + // Some safety checks to not trigger while full screen/modals are open + // Check the key and then click the corresponding button + if (!UIView.HasModalInput() + && !UIView.HasInputFocus()) { + if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { + ClickToolButton(typeof(ToggleTrafficLightsButton)); + } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneArrowsButton)); + } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneConnectorButton)); + } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(PrioritySignsButton)); + } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(JunctionRestrictionsButton)); + } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(SpeedLimitsButton)); + } + } + } + + /// For given button class type, find it in the tool palette and send click + /// Something like typeof(ToggleTrafficLightsButton) + void ClickToolButton(Type t) { + for (var i = 0; i < MENU_BUTTON_TYPES.Length; i++) { + if (MENU_BUTTON_TYPES[i] == t) { + Buttons[i].SimulateClick(); + return; + } + } + } + } } diff --git a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs index 10774e837..7bc01aa52 100644 --- a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs @@ -3,8 +3,8 @@ namespace TrafficManager.UI.MainMenu { public class ManualTrafficLightsButton : MenuToolModeButton { public override ToolMode ToolMode => ToolMode.ManualSwitch; - public override ButtonFunction Function => ButtonFunction.ManualTrafficLights; - public override string Tooltip => "Manual_traffic_lights"; - public override bool Visible => Options.timedLightsEnabled; - } -} + public override ButtonFunction Function => ButtonFunction.ManualTrafficLights; + public override string Tooltip => "Manual_traffic_lights"; + public override bool Visible => Options.timedLightsEnabled; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs b/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs index ad0530221..46d6d452a 100644 --- a/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs +++ b/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs @@ -9,11 +9,7 @@ namespace TrafficManager.UI.MainMenu { public abstract class MenuToolModeButton : MenuButton { public abstract ToolMode ToolMode { get; } - public override bool Active { - get { - return this.ToolMode.Equals(UIBase.GetTrafficManagerTool(false)?.GetToolMode()); - } - } + public override bool Active => ToolMode.Equals(UIBase.GetTrafficManagerTool(false)?.GetToolMode()); public override void OnClickInternal(UIMouseEventParameter p) { if (Active) { diff --git a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs index f8cbda17c..b5477a663 100644 --- a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs @@ -8,28 +8,9 @@ namespace TrafficManager.UI.MainMenu { public class ParkingRestrictionsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.ParkingRestrictions; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.ParkingRestrictions; - } - } - - public override string Tooltip { - get { - return "Parking_restrictions"; - } - } - - public override bool Visible { - get { - return Options.parkingRestrictionsEnabled; - } - } + public override ToolMode ToolMode => ToolMode.ParkingRestrictions; + public override ButtonFunction Function => ButtonFunction.ParkingRestrictions; + public override string Tooltip => "Parking_restrictions"; + public override bool Visible => Options.parkingRestrictionsEnabled; } } diff --git a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs index 2f5b9b3b5..9d532bc64 100644 --- a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs +++ b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs @@ -4,9 +4,9 @@ namespace TrafficManager.UI.MainMenu { public class PrioritySignsButton : MenuToolModeButton { public override ToolMode ToolMode => ToolMode.AddPrioritySigns; - public override ButtonFunction Function => ButtonFunction.PrioritySigns; - public override string Tooltip => "Add_priority_signs"; - public override bool Visible => Options.prioritySignsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyPrioritySignsTool; - } -} + public override ButtonFunction Function => ButtonFunction.PrioritySigns; + public override string Tooltip => "Add_priority_signs"; + public override bool Visible => Options.prioritySignsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyPrioritySignsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs index 0003f5f78..070d41d99 100644 --- a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs +++ b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs @@ -4,9 +4,9 @@ namespace TrafficManager.UI.MainMenu { public class SpeedLimitsButton : MenuToolModeButton { public override ToolMode ToolMode => ToolMode.SpeedLimits; - public override ButtonFunction Function => ButtonFunction.SpeedLimits; - public override string Tooltip => "Speed_limits"; - public override bool Visible => Options.customSpeedLimitsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeySpeedLimitsTool; - } -} + public override ButtonFunction Function => ButtonFunction.SpeedLimits; + public override string Tooltip => "Speed_limits"; + public override bool Visible => Options.customSpeedLimitsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeySpeedLimitsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs index f633040c3..d986507cc 100644 --- a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs @@ -8,28 +8,9 @@ namespace TrafficManager.UI.MainMenu { public class TimedTrafficLightsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.TimedLightsSelectNode; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.TimedTrafficLights; - } - } - - public override string Tooltip { - get { - return "Timed_traffic_lights"; - } - } - - public override bool Visible { - get { - return Options.timedLightsEnabled; - } - } + public override ToolMode ToolMode => ToolMode.TimedLightsSelectNode; + public override ButtonFunction Function => ButtonFunction.TimedTrafficLights; + public override string Tooltip => "Timed_traffic_lights"; + public override bool Visible => Options.timedLightsEnabled; } } diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs index 53ec43db4..cae176cf8 100644 --- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs @@ -4,9 +4,9 @@ namespace TrafficManager.UI.MainMenu { public class ToggleTrafficLightsButton : MenuToolModeButton { public override ToolMode ToolMode => ToolMode.SwitchTrafficLight; - public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; - public override string Tooltip => "Switch_traffic_lights"; - public override bool Visible => true; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyToggleTrafficLightTool; - } -} + public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; + public override string Tooltip => "Switch_traffic_lights"; + public override bool Visible => true; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyToggleTrafficLightTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs index 5163db5b4..8173774d7 100644 --- a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs @@ -8,28 +8,9 @@ namespace TrafficManager.UI.MainMenu { public class VehicleRestrictionsButton : MenuToolModeButton { - public override ToolMode ToolMode { - get { - return ToolMode.VehicleRestrictions; - } - } - - public override ButtonFunction Function { - get { - return ButtonFunction.VehicleRestrictions; - } - } - - public override string Tooltip { - get { - return "Vehicle_restrictions"; - } - } - - public override bool Visible { - get { - return Options.vehicleRestrictionsEnabled; - } - } + public override ToolMode ToolMode => ToolMode.VehicleRestrictions; + public override ButtonFunction Function => ButtonFunction.VehicleRestrictions; + public override string Tooltip => "Vehicle_restrictions"; + public override bool Visible => Options.vehicleRestrictionsEnabled; } } diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index 66366f395..2badbfe6f 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -34,7 +34,14 @@ enum StayInLaneMode { private NodeLaneMarker hoveredMarker = null; private Dictionary> currentNodeMarkers; private StayInLaneMode stayInLaneMode = StayInLaneMode.None; - //private bool initDone = false; + //private bool initDone = false; + + /// Unity frame when OnGui detected the shortcut for Stay in Lane. + /// Resets when the event is consumed or after a few frames. + private int frameStayInLanePressed = 0; + + /// Clear lane lines is Delete or Backspace + private int frameClearPressed = 0; class NodeLaneMarker { internal ushort segmentId; @@ -60,7 +67,21 @@ public LaneConnectorTool(TrafficManagerTool mainTool) : base(mainTool) { } public override void OnToolGUI(Event e) { - //Log._Debug($"TppLaneConnectorTool: OnToolGUI. SelectedNodeId={SelectedNodeId} SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); +// Log._Debug($"TppLaneConnectorTool: OnToolGUI. SelectedNodeId={SelectedNodeId} " + +// $"SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} " + +// $"HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); + + if (OptionsKeymapping.KeyLaneConnectorStayInLane.IsPressed(e)) { + frameStayInLanePressed = Time.frameCount; + // this will be consumed in RenderOverlay() if the key was pressed + // not too long ago (within 20 Unity frames or 0.33 sec) + } + + if (Input.GetKeyDown(KeyCode.Delete) || Input.GetKeyDown(KeyCode.Backspace)) { + frameClearPressed = Time.frameCount; + // this will be consumed in RenderOverlay() if the key was pressed + // not too long ago (within 20 Unity frames or 0.33 sec) + } } public override void RenderInfoOverlay(RenderManager.CameraInfo cameraInfo) { @@ -97,7 +118,7 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { continue; // do not draw if too distant List nodeMarkers; - bool hasMarkers = currentNodeMarkers.TryGetValue((ushort)nodeId, out nodeMarkers); + var hasMarkers = currentNodeMarkers.TryGetValue((ushort)nodeId, out nodeMarkers); if (!viewOnly && GetMarkerSelectionMode() == MarkerSelectionMode.None) { MainTool.DrawNodeCircle(cameraInfo, (ushort)nodeId, DefaultNodeMarkerColor, true); @@ -187,13 +208,20 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { } } - bool deleteAll = Input.GetKeyDown(KeyCode.Delete) || Input.GetKeyDown(KeyCode.Backspace); - bool stayInLane = OptionsKeymapping.KeyLaneConnectorStayInLane.IsPressed(Event.current) - && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; - if (stayInLane) + var deleteAll = frameClearPressed > 0 && (Time.frameCount - frameClearPressed) < 20; // 0.33 sec + + // Must press Shift+S (or another shortcut) within last 20 frames for this to work + var stayInLane = frameStayInLanePressed > 0 + && (Time.frameCount - frameStayInLanePressed) < 20 // 0.33 sec + && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; + + if (stayInLane) { + frameStayInLanePressed = 0; // not pressed anymore (consumed) deleteAll = true; + } if (deleteAll) { + frameClearPressed = 0; // consumed // remove all connections at selected node LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(SelectedNodeId); RefreshCurrentNodeMarkers(SelectedNodeId); diff --git a/TLM/TLM/UI/ToolMode.cs b/TLM/TLM/UI/ToolMode.cs index 8c4de00df..17518401c 100644 --- a/TLM/TLM/UI/ToolMode.cs +++ b/TLM/TLM/UI/ToolMode.cs @@ -1,19 +1,19 @@ namespace TrafficManager.UI { - public enum ToolMode { - None = 0, - SwitchTrafficLight = 1, - AddPrioritySigns = 2, - ManualSwitch = 3, - TimedLightsSelectNode = 4, - TimedLightsShowLights = 5, - LaneChange = 6, - TimedLightsAddNode = 7, - TimedLightsRemoveNode = 8, - TimedLightsCopyLights = 9, - SpeedLimits = 10, - VehicleRestrictions = 11, - LaneConnector = 12, - JunctionRestrictions = 13, - ParkingRestrictions = 14 - } + public enum ToolMode { + None = 0, + SwitchTrafficLight = 1, + AddPrioritySigns = 2, + ManualSwitch = 3, + TimedLightsSelectNode = 4, + TimedLightsShowLights = 5, + LaneChange = 6, + TimedLightsAddNode = 7, + TimedLightsRemoveNode = 8, + TimedLightsCopyLights = 9, + SpeedLimits = 10, + VehicleRestrictions = 11, + LaneConnector = 12, + JunctionRestrictions = 13, + ParkingRestrictions = 14 + } } \ No newline at end of file diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 4d5f3bf00..a9ef032c6 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -58,7 +58,7 @@ public override void Start() { Drag.width = width; Drag.height = height; Drag.enabled = !GlobalConfig.Instance.Main.MainMenuButtonPosLocked; - } + } public override void OnDestroy() { if (confDisposable != null) { @@ -124,17 +124,16 @@ public void UpdatePosition(Vector2 pos) { Invalidate(); } - public void OnGUI() { - if (!UIView.HasModalInput() - && !UIView.HasInputFocus() - && OptionsKeymapping.KeyToggleTMPEMainMenu.IsPressed(Event.current)) - { - LoadingExtension.BaseUI?.ToggleMainMenu(); - } - - // FIXME: Tooltip text is not displayed on the tool button - // var shortcutText = OptionsKeymapping.KeyToggleTMPEMainMenu.ToLocalizedString("KEYNAME"); - // tooltip = Translation.GetString("Keybind_toggle_TMPE_main_menu") + shortcutText; - } - } + public void OnGUI() { + if (!UIView.HasModalInput() + && !UIView.HasInputFocus() + && OptionsKeymapping.KeyToggleTMPEMainMenu.IsPressed(Event.current)) { + LoadingExtension.BaseUI?.ToggleMainMenu(); + } + + // FIXME: Tooltip text is not displayed on the tool button + // var shortcutText = OptionsKeymapping.KeyToggleTMPEMainMenu.ToLocalizedString("KEYNAME"); + // tooltip = Translation.GetString("Keybind_toggle_TMPE_main_menu") + shortcutText; + } + } } From f55b54e1eb099b0350486f4f35f572c7ce00d83d Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Tue, 18 Jun 2019 20:36:39 +0200 Subject: [PATCH 051/142] Fixed unlimited speed setting --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 6 ++---- TLM/TLM/Traffic/Data/SpeedLimit.cs | 2 +- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 6 +++--- TLM/TLM/UI/TextureResources.cs | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index c77a71da5..e8e982803 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -495,14 +495,12 @@ public float VehicleToCustomSpeed_Deprecated(float vehicleSpeed) { return vehicleSpeed; } - /// - /// Sets the speed limit of a given lane. - /// + /// Sets the speed limit of a given lane. /// /// /// /// - /// + /// Game speed units, 0=unlimited /// public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, float speedLimit) { if (!MayHaveCustomSpeedLimits(laneInfo)) { diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index f6bcb1908..c7c8f5a54 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -83,7 +83,7 @@ public static bool IsZero(float speed) { } public static bool IsValidRange(float speed) { - return speed >= LOWER_SPEED && speed <= UPPER_SPEED; + return IsZero(speed) || (speed >= LOWER_SPEED && speed <= UPPER_SPEED); } /// diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 8736b115f..853473b90 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -553,11 +553,11 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo // Draw something right here, the road sign texture GUI.color = guiColor; - var displayLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key); + var displayLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key); var tex = TextureResources.GetSpeedLimitTexture(displayLimit); - GUI.DrawTexture(boundingBox, tex); + GUI.DrawTexture(boundingBox, tex); - if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { + if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { // change the speed limit to the selected one //Log._Debug($"Setting speed limit of segment {segmentId}, dir {e.Key.ToString()} to {speedLimitToSet}"); SpeedLimitManager.Instance.SetSpeedLimit(segmentId, e.Key, currentPaletteSpeedLimit); diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index ca0729759..fe6a378bb 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -220,8 +220,8 @@ static TextureResources() { /// Ingame speed /// The texture, hopefully it existed public static Texture2D GetSpeedLimitTexture(float speedLimit) { - if (speedLimit >= SpeedLimitManager.MAX_SPEED) { - // Special value for max speed + // Special value for max speed, give it 5% margin for safety 950km/h+ + if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { return SpeedLimitTextures[0]; } var index = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph From 8bfd1e683c63189b84111458446c3fd24d245900 Mon Sep 17 00:00:00 2001 From: Dmytro Lytovchenko Date: Wed, 19 Jun 2019 00:09:33 +0200 Subject: [PATCH 052/142] Custom speed limits are not applied while loading save game Co-Authored-By: Krzysztof --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index e8e982803..f5af8b42c 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -760,7 +760,7 @@ public bool LoadData(List data) { Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: " + $"Custom speed limit of segment {segmentId} info ({info}, name={info?.name}, " + $"lanes={info?.m_lanes} is {customSpeedLimit}"); - if (customSpeedLimit < 0) { + if (customSpeedLimit > 0) { // lane speed limit differs from default speed limit Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: " + $"lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); From 8533bef7c4a216b66293cacfb85dbd03925ef2e8 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Wed, 19 Jun 2019 00:32:20 +0200 Subject: [PATCH 053/142] Use IsValidRange instead of > 0 --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index f5af8b42c..19caa57a6 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -760,7 +760,7 @@ public bool LoadData(List data) { Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: " + $"Custom speed limit of segment {segmentId} info ({info}, name={info?.name}, " + $"lanes={info?.m_lanes} is {customSpeedLimit}"); - if (customSpeedLimit > 0) { + if (SpeedLimit.IsValidRange(customSpeedLimit)) { // lane speed limit differs from default speed limit Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: " + $"lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); From 02d16c936db14f479dbe993830363bc300f4dc2f Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Wed, 19 Jun 2019 00:34:47 +0200 Subject: [PATCH 054/142] Check this.IsVisible before checking keys --- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 32 +++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index 1222c17bc..69b4377f5 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -209,23 +209,25 @@ public void UpdatePosition(Vector2 pos) { } public void OnGUI() { + // Return if modal window is active or the main menu is hidden + if (!this.isVisible || UIView.HasModalInput() || UIView.HasInputFocus()) { + return; + } + // Some safety checks to not trigger while full screen/modals are open // Check the key and then click the corresponding button - if (!UIView.HasModalInput() - && !UIView.HasInputFocus()) { - if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { - ClickToolButton(typeof(ToggleTrafficLightsButton)); - } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { - ClickToolButton(typeof(LaneArrowsButton)); - } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(LaneConnectorButton)); - } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(PrioritySignsButton)); - } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(JunctionRestrictionsButton)); - } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(SpeedLimitsButton)); - } + if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { + ClickToolButton(typeof(ToggleTrafficLightsButton)); + } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneArrowsButton)); + } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneConnectorButton)); + } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(PrioritySignsButton)); + } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(JunctionRestrictionsButton)); + } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(SpeedLimitsButton)); } } From 4679569c91c9c32b537c8e4e39baeee40f60492b Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Wed, 19 Jun 2019 02:38:58 +0200 Subject: [PATCH 055/142] Added dummy button to speed limits; added 90 MPH --- TLM/TLM/Traffic/Data/SpeedLimit.cs | 4 +- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 81 +++++++++++++++++--------- TLM/TLM/UI/TextureResources.cs | 4 ++ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index c7c8f5a54..29b6ed254 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -20,11 +20,13 @@ public struct SpeedLimit { private const ushort LOWER_KMPH = 10; public const ushort UPPER_KMPH = 140; private const ushort KMPH_STEP = 10; + public const int BREAK_PALETTE_COLUMN_KMPH = 8; // palette shows N in a row, then break and another row private const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph private const ushort LOWER_MPH = 5; - private const ushort UPPER_MPH = 85; + private const ushort UPPER_MPH = 90; private const ushort MPH_STEP = 5; + public const int BREAK_PALETTE_COLUMN_MPH = 10; // palette shows M in a row, then break and another row private const float LOWER_SPEED = 0.1f; private const float UPPER_SPEED = 2 * 10.0f; // 1000 km/h diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 853473b90..a11fd1d8e 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -35,7 +35,7 @@ public class SpeedLimitsTool : SubTool { private readonly int guiSpeedSignSize = 90; private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 7 * 105, 225)); - private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 280, 400, 400)); + private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); private HashSet currentlyVisibleSegmentIds; private bool defaultsWindowVisible = false; private int currentInfoIndex = -1; @@ -200,15 +200,6 @@ private void _guiDefaultsWindow(int num) { } //Log._Debug($"currentInfoIndex={currentInfoIndex} currentSpeedLimitIndex={currentSpeedLimitIndex}"); - // Close button. TODO: Make more visible or obey 'Esc' pressed or something - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("X", GUILayout.Width(50))) { - defaultsWindowVisible = false; - } - - GUILayout.EndHorizontal(); - // Road type label GUILayout.BeginVertical(); GUILayout.Space(10); @@ -315,6 +306,13 @@ private void _guiDefaultsWindow(int num) { GUILayout.Space(10); GUILayout.BeginHorizontal(); + + // Close button. TODO: Make more visible or obey 'Esc' pressed or something + GUILayout.FlexibleSpace(); + if (GUILayout.Button("X", GUILayout.Width(80))) { + defaultsWindowVisible = false; + } + GUILayout.FlexibleSpace(); if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { SpeedLimitManager.Instance.FixCurrentSpeedLimits(info); @@ -372,8 +370,9 @@ private void _guiSpeedLimitsWindow(int num) { allSpeedLimits.Add(0); // add last item: no limit var showMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; - var column = 0u; // break palette to a new line at 8 or something - var breakColumn = showMph ? 8 : 7; // MPH have more items so break 1 column later + var column = 0u; // break palette to a new line at breakColumn + var breakColumn = showMph ? SpeedLimit.BREAK_PALETTE_COLUMN_MPH + : SpeedLimit.BREAK_PALETTE_COLUMN_KMPH; foreach (var speedLimit in allSpeedLimits) { // Highlight palette item if it is very close to its float speed @@ -381,41 +380,65 @@ private void _guiSpeedLimitsWindow(int num) { GUI.color = Color.gray; } - // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added - GUILayout.BeginVertical(); - var signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); - if (GUILayout.Button( - TextureResources.GetSpeedLimitTexture(speedLimit), - GUILayout.Width(signSize), - GUILayout.Height(signSize))) { - currentPaletteSpeedLimit = speedLimit; - } - // For MPH setting display KM/H below, for KM/H setting display MPH - GUILayout.Label(showMph ? SpeedLimit.ToKmphPreciseString(speedLimit) - : SpeedLimit.ToMphPreciseString(speedLimit)); - GUILayout.EndVertical(); + _guiSpeedLimitsWindow_AddButton(showMph, speedLimit); GUI.color = oldColor; // TODO: This can be calculated from SpeedLimit MPH or KMPH limit constants - if (column == breakColumn) { + column++; + if (column % breakColumn == 0) { GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); - } - column++; + } } + while (column % breakColumn != 0) { + // The last row is not finished, might want to add a spacer + _guiSpeedLimitsWindow_AddDummy(); + column++; + } GUILayout.EndHorizontal(); - if (GUILayout.Button(Translation.GetString("Default_speed_limits"))) { + GUILayout.BeginHorizontal(); + if (GUILayout.Button(Translation.GetString("Default_speed_limits"), + GUILayout.Width(200))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Defaults"); defaultsWindowVisible = true; } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); showLimitsPerLane = GUILayout.Toggle(showLimitsPerLane, Translation.GetString("Show_lane-wise_speed_limits")); DragWindow(ref windowRect); } + /// Like addButton helper below, but adds unclickable space of the same size + private void _guiSpeedLimitsWindow_AddDummy() { + GUILayout.BeginVertical(); + var signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); + GUILayout.Button( null as Texture, GUILayout.Width(signSize), GUILayout.Height(signSize)); + GUILayout.EndVertical(); + } + + /// Helper to create speed limit sign + label below converted to the opposite unit + /// Config value from GlobalConfig.I.M.ShowMPH + /// The float speed to show + private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { + // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added + GUILayout.BeginVertical(); + var signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); + if (GUILayout.Button( + TextureResources.GetSpeedLimitTexture(speedLimit), + GUILayout.Width(signSize), + GUILayout.Height(signSize))) { + currentPaletteSpeedLimit = speedLimit; + } + // For MPH setting display KM/H below, for KM/H setting display MPH + GUILayout.Label(showMph ? SpeedLimit.ToKmphPreciseString(speedLimit) + : SpeedLimit.ToMphPreciseString(speedLimit)); + GUILayout.EndVertical(); + } + private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { if (viewOnly && !Options.speedLimitsOverlay) { return false; diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index fe6a378bb..0e834ad00 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -227,6 +227,10 @@ public static Texture2D GetSpeedLimitTexture(float speedLimit) { var index = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? SpeedLimit.ToMphRounded(speedLimit) : SpeedLimit.ToKmphRounded(speedLimit); + // Trim by kmph because UPPER_KMPH is the max road sign that we have + if (index > SpeedLimit.UPPER_KMPH) { + Log.Info($"Trimming speed={speedLimit} index={index} to 140"); + } var trimIndex = Math.Min(SpeedLimit.UPPER_KMPH, Math.Max((ushort)0, index)); // Log._Debug($"texture for {speedLimit} is {index} trim={trimIndex}"); return SpeedLimitTextures[trimIndex]; From 0d8cd8516f7bc6461b9576ff3bed9cf5987249f4 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Wed, 19 Jun 2019 03:31:18 +0200 Subject: [PATCH 056/142] Remove the deprecated functions, unused after we started using float speeds --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 32 ----------------------- 1 file changed, 32 deletions(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index 19caa57a6..cd2b7a6ec 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -248,28 +248,6 @@ public float ToGameSpeedLimit(float customSpeedLimit) { : customSpeedLimit; } - /// - /// Converts a lane speed limit to a custom speed limit. - /// - /// - /// - public ushort LaneToCustomSpeedLimit_Deprecated(float laneSpeedLimit, bool roundToSignLimits=true) { - laneSpeedLimit /= 2f; // 1 == 100 km/h - - if (! roundToSignLimits) { - return (ushort)Mathf.Round(laneSpeedLimit * 100f); - } - - // translate the floating point speed limit into our discrete version - ushort speedLimit = 0; - if (laneSpeedLimit < 0.15f) - speedLimit = 10; - else if (laneSpeedLimit < 1.35f) - speedLimit = (ushort)((ushort)Mathf.Round(laneSpeedLimit * 10f) * 10u); - - return speedLimit; - } - /// /// Explicitly stores currently set speed limits for all segments of the specified NetInfo /// @@ -485,16 +463,6 @@ private void UpdateNetInfoGameSpeedLimit(NetInfo info, float gameSpeedLimit) { } } - /// - /// Converts a vehicle's velocity to a custom speed. - /// - /// - /// - public float VehicleToCustomSpeed_Deprecated(float vehicleSpeed) { - // return LaneToCustomSpeedLimit(vehicleSpeed / 8f, false); - return vehicleSpeed; - } - /// Sets the speed limit of a given lane. /// /// From b400887b10e21490323c79523d27521d92de8f61 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Wed, 19 Jun 2019 03:35:20 +0200 Subject: [PATCH 057/142] Remove newline between variables --- TLM/TLM/State/ConfigData/Main.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/TLM/TLM/State/ConfigData/Main.cs b/TLM/TLM/State/ConfigData/Main.cs index cc0b28cc5..579a4eb2e 100644 --- a/TLM/TLM/State/ConfigData/Main.cs +++ b/TLM/TLM/State/ConfigData/Main.cs @@ -10,7 +10,6 @@ public class Main { /// Main menu button position /// public int MainMenuButtonX = 464; - public int MainMenuButtonY = 10; public bool MainMenuButtonPosLocked = false; @@ -18,7 +17,6 @@ public class Main { /// Main menu position /// public int MainMenuX = MainMenuPanel.DEFAULT_MENU_X; - public int MainMenuY = MainMenuPanel.DEFAULT_MENU_Y; public bool MainMenuPosLocked = false; From cf2a754d190a435a93b0d30f186c1e6b58d2db98 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Wed, 19 Jun 2019 03:41:21 +0200 Subject: [PATCH 058/142] Removed bool variable which was always true --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index cd2b7a6ec..d20df6827 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -763,7 +763,6 @@ public bool LoadData(List data) { } public bool LoadData(Dictionary data) { - const bool success = true; Log.Info($"Loading custom default speed limit data. {data.Count} elements"); foreach (var e in data) { NetInfo netInfo; @@ -775,7 +774,7 @@ public bool LoadData(Dictionary data) { SetCustomNetInfoSpeedLimit(netInfo, e.Value); } } - return success; + return true; // true = success } Dictionary ICustomDataManager>.SaveData(ref bool success) { From 0f468b1f0566b18ee12a51afe169e4396533af63 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Wed, 19 Jun 2019 19:06:30 -0600 Subject: [PATCH 059/142] Dynamic Window Width & Center Rows --- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index a11fd1d8e..677071d9e 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -34,7 +34,7 @@ public class SpeedLimitsTool : SubTool { /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH private readonly int guiSpeedSignSize = 90; - private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 7 * 105, 225)); + private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 7 * 95, 225)); private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); private HashSet currentlyVisibleSegmentIds; private bool defaultsWindowVisible = false; @@ -73,6 +73,7 @@ public override void OnToolGUI(Event e) { var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? Translation.GetString("Miles_per_hour") : Translation.GetString("Kilometers_per_hour")) + ")"; + windowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * 95 : 8 * 95; windowRect = GUILayout.Window(254, windowRect, _guiSpeedLimitsWindow, Translation.GetString("Speed_limits") + unitTitle, WindowStyle); @@ -364,6 +365,7 @@ private void UpdateRoadTex(NetInfo info) { /// private void _guiSpeedLimitsWindow(int num) { GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); var oldColor = GUI.color; var allSpeedLimits = SpeedLimit.EnumerateSpeedLimits(SpeedUnit.CurrentlyConfigured); @@ -386,19 +388,17 @@ private void _guiSpeedLimitsWindow(int num) { // TODO: This can be calculated from SpeedLimit MPH or KMPH limit constants column++; if (column % breakColumn == 0) { + GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); } } - - while (column % breakColumn != 0) { - // The last row is not finished, might want to add a spacer - _guiSpeedLimitsWindow_AddDummy(); - column++; - } + GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); if (GUILayout.Button(Translation.GetString("Default_speed_limits"), GUILayout.Width(200))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Defaults"); @@ -407,7 +407,11 @@ private void _guiSpeedLimitsWindow(int num) { GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); showLimitsPerLane = GUILayout.Toggle(showLimitsPerLane, Translation.GetString("Show_lane-wise_speed_limits")); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); DragWindow(ref windowRect); } From 1c57b4bfff437a6e249adf0da0fd56840442266a Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Wed, 19 Jun 2019 19:11:01 -0600 Subject: [PATCH 060/142] Specify Texture Size --- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 677071d9e..fade2700b 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -34,7 +34,7 @@ public class SpeedLimitsTool : SubTool { /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH private readonly int guiSpeedSignSize = 90; - private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 7 * 95, 225)); + private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * 95, 225)); private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); private HashSet currentlyVisibleSegmentIds; private bool defaultsWindowVisible = false; @@ -73,7 +73,7 @@ public override void OnToolGUI(Event e) { var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? Translation.GetString("Miles_per_hour") : Translation.GetString("Kilometers_per_hour")) + ")"; - windowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * 95 : 8 * 95; + windowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * (guiSpeedSignSize + 5) : 8 * (guiSpeedSignSize + 5); windowRect = GUILayout.Window(254, windowRect, _guiSpeedLimitsWindow, Translation.GetString("Speed_limits") + unitTitle, WindowStyle); From a9a22170b1838a134a220a1d3372f01df46d859c Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Wed, 19 Jun 2019 19:14:35 -0600 Subject: [PATCH 061/142] Make Constant --- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index fade2700b..61b1d6405 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -22,6 +22,10 @@ namespace TrafficManager.UI.SubTools { public class SpeedLimitsTool : SubTool { + /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH + private const int GuiSpeedSignSize = 90; + private readonly float speedLimitSignSize = 80f; + private bool _cursorInSecondaryPanel; /// Currently selected speed limit on the limits palette @@ -29,12 +33,8 @@ public class SpeedLimitsTool : SubTool { private bool overlayHandleHovered; private Dictionary> segmentCenterByDir = new Dictionary>(); - private readonly float speedLimitSignSize = 80f; - - /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH - private readonly int guiSpeedSignSize = 90; - private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * 95, 225)); + private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * (GuiSpeedSignSize + 5), 225)); private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); private HashSet currentlyVisibleSegmentIds; private bool defaultsWindowVisible = false; @@ -43,7 +43,7 @@ public class SpeedLimitsTool : SubTool { private Texture2D RoadTexture { get { if (roadTexture == null) { - roadTexture = new Texture2D(guiSpeedSignSize, guiSpeedSignSize); + roadTexture = new Texture2D(GuiSpeedSignSize, GuiSpeedSignSize); } return roadTexture; } @@ -73,7 +73,7 @@ public override void OnToolGUI(Event e) { var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? Translation.GetString("Miles_per_hour") : Translation.GetString("Kilometers_per_hour")) + ")"; - windowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * (guiSpeedSignSize + 5) : 8 * (guiSpeedSignSize + 5); + windowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * (GuiSpeedSignSize + 5) : 8 * (GuiSpeedSignSize + 5); windowRect = GUILayout.Window(254, windowRect, _guiSpeedLimitsWindow, Translation.GetString("Speed_limits") + unitTitle, WindowStyle); @@ -228,7 +228,7 @@ private void _guiDefaultsWindow(int num) { GUILayout.FlexibleSpace(); // NetInfo thumbnail - GUILayout.Box(RoadTexture, GUILayout.Height(guiSpeedSignSize)); + GUILayout.Box(RoadTexture, GUILayout.Height(GuiSpeedSignSize)); GUILayout.FlexibleSpace(); GUILayout.EndVertical(); @@ -279,8 +279,8 @@ private void _guiDefaultsWindow(int num) { // speed limit sign GUILayout.Box(TextureResources.GetSpeedLimitTexture(currentSpeedLimit), - GUILayout.Width(guiSpeedSignSize), - GUILayout.Height(guiSpeedSignSize)); + GUILayout.Width(GuiSpeedSignSize), + GUILayout.Height(GuiSpeedSignSize)); GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? Translation.GetString("Miles_per_hour") : Translation.GetString("Kilometers_per_hour")); @@ -419,7 +419,7 @@ private void _guiSpeedLimitsWindow(int num) { /// Like addButton helper below, but adds unclickable space of the same size private void _guiSpeedLimitsWindow_AddDummy() { GUILayout.BeginVertical(); - var signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); + var signSize = TrafficManagerTool.AdaptWidth(GuiSpeedSignSize); GUILayout.Button( null as Texture, GUILayout.Width(signSize), GUILayout.Height(signSize)); GUILayout.EndVertical(); } @@ -430,7 +430,7 @@ private void _guiSpeedLimitsWindow_AddDummy() { private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added GUILayout.BeginVertical(); - var signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); + var signSize = TrafficManagerTool.AdaptWidth(GuiSpeedSignSize); if (GUILayout.Button( TextureResources.GetSpeedLimitTexture(speedLimit), GUILayout.Width(signSize), From c2f4798ccccc9580d07ac98cce6d9f1edd38324d Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Wed, 19 Jun 2019 21:18:18 -0600 Subject: [PATCH 062/142] Add MPH Signs --- TLM/TLM/Resources/{0.png => 0_kmh.png} | Bin TLM/TLM/Resources/0_mph.png | Bin 0 -> 4205 bytes TLM/TLM/Resources/{100.png => 100_kmh.png} | Bin TLM/TLM/Resources/{105.png => 105_kmh.png} | Bin TLM/TLM/Resources/{10.png => 10_kmh.png} | Bin TLM/TLM/Resources/10_mph.png | Bin 0 -> 4704 bytes TLM/TLM/Resources/{110.png => 110_kmh.png} | Bin TLM/TLM/Resources/{115.png => 115_kmh.png} | Bin TLM/TLM/Resources/{120.png => 120_kmh.png} | Bin TLM/TLM/Resources/{125.png => 125_kmh.png} | Bin TLM/TLM/Resources/{130.png => 130_kmh.png} | Bin TLM/TLM/Resources/{135.png => 135_kmh.png} | Bin TLM/TLM/Resources/{140.png => 140_kmh.png} | Bin TLM/TLM/Resources/{15.png => 15_kmh.png} | Bin TLM/TLM/Resources/15_mph.png | Bin 0 -> 4502 bytes TLM/TLM/Resources/{20.png => 20_kmh.png} | Bin TLM/TLM/Resources/20_mph.png | Bin 0 -> 5398 bytes TLM/TLM/Resources/{25.png => 25_kmh.png} | Bin TLM/TLM/Resources/25_mph.png | Bin 0 -> 5230 bytes TLM/TLM/Resources/{30.png => 30_kmh.png} | Bin TLM/TLM/Resources/30_mph.png | Bin 0 -> 5477 bytes TLM/TLM/Resources/{35.png => 35_kmh.png} | Bin TLM/TLM/Resources/35_mph.png | Bin 0 -> 5282 bytes TLM/TLM/Resources/{40.png => 40_kmh.png} | Bin TLM/TLM/Resources/40_mph.png | Bin 0 -> 4959 bytes TLM/TLM/Resources/{45.png => 45_kmh.png} | Bin TLM/TLM/Resources/45_mph.png | Bin 0 -> 4686 bytes TLM/TLM/Resources/{50.png => 50_kmh.png} | Bin TLM/TLM/Resources/50_mph.png | Bin 0 -> 5335 bytes TLM/TLM/Resources/{55.png => 55_kmh.png} | Bin TLM/TLM/Resources/55_mph.png | Bin 0 -> 5055 bytes TLM/TLM/Resources/{5.png => 5_kmh.png} | Bin TLM/TLM/Resources/5_mph.png | Bin 0 -> 4369 bytes TLM/TLM/Resources/{60.png => 60_kmh.png} | Bin TLM/TLM/Resources/60_mph.png | Bin 0 -> 5520 bytes TLM/TLM/Resources/{65.png => 65_kmh.png} | Bin TLM/TLM/Resources/65_mph.png | Bin 0 -> 5283 bytes TLM/TLM/Resources/{70.png => 70_kmh.png} | Bin TLM/TLM/Resources/70_mph.png | Bin 0 -> 5000 bytes TLM/TLM/Resources/{75.png => 75_kmh.png} | Bin TLM/TLM/Resources/75_mph.png | Bin 0 -> 4768 bytes TLM/TLM/Resources/{80.png => 80_kmh.png} | Bin TLM/TLM/Resources/80_mph.png | Bin 0 -> 5560 bytes TLM/TLM/Resources/{85.png => 85_kmh.png} | Bin TLM/TLM/Resources/85_mph.png | Bin 0 -> 5419 bytes TLM/TLM/Resources/{90.png => 90_kmh.png} | Bin TLM/TLM/Resources/90_mph.png | Bin 0 -> 5507 bytes TLM/TLM/Resources/{95.png => 95_kmh.png} | Bin TLM/TLM/TLM.csproj | 81 ++++++++++++--------- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 10 ++- TLM/TLM/UI/TextureResources.cs | 23 ++++-- 51 files changed, 69 insertions(+), 45 deletions(-) rename TLM/TLM/Resources/{0.png => 0_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/0_mph.png rename TLM/TLM/Resources/{100.png => 100_kmh.png} (100%) rename TLM/TLM/Resources/{105.png => 105_kmh.png} (100%) rename TLM/TLM/Resources/{10.png => 10_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/10_mph.png rename TLM/TLM/Resources/{110.png => 110_kmh.png} (100%) rename TLM/TLM/Resources/{115.png => 115_kmh.png} (100%) rename TLM/TLM/Resources/{120.png => 120_kmh.png} (100%) rename TLM/TLM/Resources/{125.png => 125_kmh.png} (100%) rename TLM/TLM/Resources/{130.png => 130_kmh.png} (100%) rename TLM/TLM/Resources/{135.png => 135_kmh.png} (100%) rename TLM/TLM/Resources/{140.png => 140_kmh.png} (100%) rename TLM/TLM/Resources/{15.png => 15_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/15_mph.png rename TLM/TLM/Resources/{20.png => 20_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/20_mph.png rename TLM/TLM/Resources/{25.png => 25_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/25_mph.png rename TLM/TLM/Resources/{30.png => 30_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/30_mph.png rename TLM/TLM/Resources/{35.png => 35_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/35_mph.png rename TLM/TLM/Resources/{40.png => 40_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/40_mph.png rename TLM/TLM/Resources/{45.png => 45_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/45_mph.png rename TLM/TLM/Resources/{50.png => 50_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/50_mph.png rename TLM/TLM/Resources/{55.png => 55_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/55_mph.png rename TLM/TLM/Resources/{5.png => 5_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/5_mph.png rename TLM/TLM/Resources/{60.png => 60_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/60_mph.png rename TLM/TLM/Resources/{65.png => 65_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/65_mph.png rename TLM/TLM/Resources/{70.png => 70_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/70_mph.png rename TLM/TLM/Resources/{75.png => 75_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/75_mph.png rename TLM/TLM/Resources/{80.png => 80_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/80_mph.png rename TLM/TLM/Resources/{85.png => 85_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/85_mph.png rename TLM/TLM/Resources/{90.png => 90_kmh.png} (100%) create mode 100644 TLM/TLM/Resources/90_mph.png rename TLM/TLM/Resources/{95.png => 95_kmh.png} (100%) diff --git a/TLM/TLM/Resources/0.png b/TLM/TLM/Resources/0_kmh.png similarity index 100% rename from TLM/TLM/Resources/0.png rename to TLM/TLM/Resources/0_kmh.png diff --git a/TLM/TLM/Resources/0_mph.png b/TLM/TLM/Resources/0_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..28f5a16e0c8f229d065cb306a7518ee145ab80a5 GIT binary patch literal 4205 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/100.png b/TLM/TLM/Resources/100_kmh.png similarity index 100% rename from TLM/TLM/Resources/100.png rename to TLM/TLM/Resources/100_kmh.png diff --git a/TLM/TLM/Resources/105.png b/TLM/TLM/Resources/105_kmh.png similarity index 100% rename from TLM/TLM/Resources/105.png rename to TLM/TLM/Resources/105_kmh.png diff --git a/TLM/TLM/Resources/10.png b/TLM/TLM/Resources/10_kmh.png similarity index 100% rename from TLM/TLM/Resources/10.png rename to TLM/TLM/Resources/10_kmh.png diff --git a/TLM/TLM/Resources/10_mph.png b/TLM/TLM/Resources/10_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..99328d12565304c68e26690dde9bc1988cc17cec GIT binary patch literal 4704 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/110.png b/TLM/TLM/Resources/110_kmh.png similarity index 100% rename from TLM/TLM/Resources/110.png rename to TLM/TLM/Resources/110_kmh.png diff --git a/TLM/TLM/Resources/115.png b/TLM/TLM/Resources/115_kmh.png similarity index 100% rename from TLM/TLM/Resources/115.png rename to TLM/TLM/Resources/115_kmh.png diff --git a/TLM/TLM/Resources/120.png b/TLM/TLM/Resources/120_kmh.png similarity index 100% rename from TLM/TLM/Resources/120.png rename to TLM/TLM/Resources/120_kmh.png diff --git a/TLM/TLM/Resources/125.png b/TLM/TLM/Resources/125_kmh.png similarity index 100% rename from TLM/TLM/Resources/125.png rename to TLM/TLM/Resources/125_kmh.png diff --git a/TLM/TLM/Resources/130.png b/TLM/TLM/Resources/130_kmh.png similarity index 100% rename from TLM/TLM/Resources/130.png rename to TLM/TLM/Resources/130_kmh.png diff --git a/TLM/TLM/Resources/135.png b/TLM/TLM/Resources/135_kmh.png similarity index 100% rename from TLM/TLM/Resources/135.png rename to TLM/TLM/Resources/135_kmh.png diff --git a/TLM/TLM/Resources/140.png b/TLM/TLM/Resources/140_kmh.png similarity index 100% rename from TLM/TLM/Resources/140.png rename to TLM/TLM/Resources/140_kmh.png diff --git a/TLM/TLM/Resources/15.png b/TLM/TLM/Resources/15_kmh.png similarity index 100% rename from TLM/TLM/Resources/15.png rename to TLM/TLM/Resources/15_kmh.png diff --git a/TLM/TLM/Resources/15_mph.png b/TLM/TLM/Resources/15_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c8d1320002b2bd5159ced5f4d606a32518897c GIT binary patch literal 4502 zcmb_gc{CK<-ydUS5XNN7)?i3TN%UA7YcrK4F&PZS$b?B@WE)JjkdR2KXUrh7W+JFSZeHF7AK;ax{nLzB@v0DuG%Wo7OZ_H6n2uCu(MOwyd` z%D$cENjJVGsXv?mFW(gckpgfj?Uw*YJ_oX;075`n3Zj|2vD2J$CpQz>eyeRu6HvbC`Mm4olZ#^abz>^TV=Fl&svqbJ?+K(UGXAHY zH0yOft4wbjk_R;QXX(znp3CuhD}SgPcPdjuCr$U$&=J1vt}@+ulMi%u3yA(kuC6mT zd@=o$YEshtWYeAb>FKLPKuE&E!otYU-y4CP_4V1N(n#-*sP4Ql0lIWoI?Y(0(%-wd z_|nkO5R=TG&ql)Q>gwjVx5qzEP03WDSzFVGk}tz_ZeJ|^G$I5)7~85sEs2XEktR`a zGxAP%lXFl}^v))^Bi0<2y7sj#iN+>Ue8!SycC+Wr8T>)*d~wzU48fIP+!ZzXYihOg zZaKEhSH9w*TM=1oCRSPH0&Uu{Z*pi~VHR6K6EN&e3eulPD4cW}elyY;e32

QDnq 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/25.png b/TLM/TLM/Resources/25_kmh.png similarity index 100% rename from TLM/TLM/Resources/25.png rename to TLM/TLM/Resources/25_kmh.png diff --git a/TLM/TLM/Resources/25_mph.png b/TLM/TLM/Resources/25_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..d4f601ff1179c6c7629f095e118418a998e77198 GIT binary patch literal 5230 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 a)VDZSRBbIYn66Am5iIX8NLL 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 G7y3WYk91=I literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/50.png b/TLM/TLM/Resources/50_kmh.png similarity index 100% rename from TLM/TLM/Resources/50.png rename to TLM/TLM/Resources/50_kmh.png diff --git a/TLM/TLM/Resources/50_mph.png b/TLM/TLM/Resources/50_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..c5044d0c4c2d4d5ee007178fff0df5268a4b8b29 GIT binary patch literal 5335 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/55.png b/TLM/TLM/Resources/55_kmh.png similarity index 100% rename from TLM/TLM/Resources/55.png rename to TLM/TLM/Resources/55_kmh.png diff --git a/TLM/TLM/Resources/55_mph.png b/TLM/TLM/Resources/55_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..6f07df67e868b17306b94c0c60b59f24bd834f7a GIT binary patch literal 5055 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/60.png b/TLM/TLM/Resources/60_kmh.png similarity index 100% rename from TLM/TLM/Resources/60.png rename to TLM/TLM/Resources/60_kmh.png diff --git a/TLM/TLM/Resources/60_mph.png b/TLM/TLM/Resources/60_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..59a7bfbcfc1c4f4422575105bbd6c0cc5288583a GIT binary patch literal 5520 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 E7mBc1VE_OC literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/65.png b/TLM/TLM/Resources/65_kmh.png similarity index 100% rename from TLM/TLM/Resources/65.png rename to TLM/TLM/Resources/65_kmh.png diff --git a/TLM/TLM/Resources/65_mph.png b/TLM/TLM/Resources/65_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..2c6d5e3a049a7c5666d18c09e5ecea29a65d3e74 GIT binary patch literal 5283 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/70.png b/TLM/TLM/Resources/70_kmh.png similarity index 100% rename from TLM/TLM/Resources/70.png rename to TLM/TLM/Resources/70_kmh.png diff --git a/TLM/TLM/Resources/70_mph.png b/TLM/TLM/Resources/70_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..1d7416e15a16bb775e214af367d751eb0948373f GIT binary patch literal 5000 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/80.png b/TLM/TLM/Resources/80_kmh.png similarity index 100% rename from TLM/TLM/Resources/80.png rename to TLM/TLM/Resources/80_kmh.png diff --git a/TLM/TLM/Resources/80_mph.png b/TLM/TLM/Resources/80_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..008d1cc24d5095f8e15525163823ed5c45d76b4f GIT binary patch literal 5560 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 I>Nv&y2hZO(r~m)} literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/90.png b/TLM/TLM/Resources/90_kmh.png similarity index 100% rename from TLM/TLM/Resources/90.png rename to TLM/TLM/Resources/90_kmh.png diff --git a/TLM/TLM/Resources/90_mph.png b/TLM/TLM/Resources/90_mph.png new file mode 100644 index 0000000000000000000000000000000000000000..b842d07f80490542a4549870d056169c8c1f7f7b GIT binary patch literal 5507 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/95.png b/TLM/TLM/Resources/95_kmh.png similarity index 100% rename from TLM/TLM/Resources/95.png rename to TLM/TLM/Resources/95_kmh.png diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 463cae0b6..371348a6a 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -365,24 +365,6 @@ - - - - - - - - - - - - - - - - - - @@ -399,9 +381,6 @@ - - - @@ -465,18 +444,54 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 61b1d6405..9c611f08a 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -434,7 +434,7 @@ private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { if (GUILayout.Button( TextureResources.GetSpeedLimitTexture(speedLimit), GUILayout.Width(signSize), - GUILayout.Height(signSize))) { + showMph ? GUILayout.Height(signSize * 1.25f) : GUILayout.Height(signSize))) { currentPaletteSpeedLimit = speedLimit; } // For MPH setting display KM/H below, for KM/H setting display MPH @@ -505,9 +505,10 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo } var laneSpeedLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId); - var hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture( + var hoveredHandle = MainTool.DrawGenericOverlayGridTexture( TextureResources.GetSpeedLimitTexture(laneSpeedLimit), - camPos, zero, f, xu, yu, x, 0, speedLimitSignSize, + camPos, zero, f, f, xu, yu, x, 0, speedLimitSignSize, + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? speedLimitSignSize * 1.25f : speedLimitSignSize, !viewOnly); if (!viewOnly @@ -569,7 +570,8 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo var guiColor = GUI.color; var boundingBox = new Rect(screenPos.x - (size / 2), screenPos.y - (size / 2), - size, size); + size, + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? size * 1.25f : size); var hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index 0e834ad00..4df8cd41e 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -47,7 +47,7 @@ public class TextureResources { public static readonly Texture2D ClockPlayTexture2D; public static readonly Texture2D ClockPauseTexture2D; public static readonly Texture2D ClockTestTexture2D; - public static readonly IDictionary SpeedLimitTextures; + public static readonly IDictionary SpeedLimitTextures; public static readonly IDictionary> VehicleRestrictionTextures; public static readonly IDictionary VehicleInfoSignTextures; public static readonly IDictionary ParkingRestrictionTextures; @@ -135,14 +135,20 @@ static TextureResources() { ClockTestTexture2D = LoadDllResource("clock_test.png", 512, 512); // TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor - SpeedLimitTextures = new TinyDictionary(); + SpeedLimitTextures = new TinyDictionary(); // Load shared speed limit signs for Kmph and Mph - // Assumes that signs from 0 to 140 with step 5 exist, 0.png denotes no limit sign + // Assumes that signs from 0 to 140 with step 5 exist, 0 denotes no limit sign for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) { - var resource = LoadDllResource(speedLimit + ".png", 200, 200); - SpeedLimitTextures.Add(speedLimit, - resource ?? SpeedLimitTextures[5]); + var resource = LoadDllResource(speedLimit + "_kmh.png", 200, 200); + SpeedLimitTextures.Add(speedLimit + "_kmh", + resource ?? SpeedLimitTextures["5_kmh"]); + } + // Signs from 0 to 90 for MPH + for (var speedLimit = 0; speedLimit <= 90; speedLimit += 5) { + var resource = LoadDllResource(speedLimit + "_mph.png", 200, 200); + SpeedLimitTextures.Add(speedLimit + "_mph", + resource ?? SpeedLimitTextures["5_mph"]); } VehicleRestrictionTextures = new TinyDictionary>(); @@ -220,9 +226,10 @@ static TextureResources() { /// Ingame speed /// The texture, hopefully it existed public static Texture2D GetSpeedLimitTexture(float speedLimit) { + var suffix = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? "_mph" : "_kmh"; // Special value for max speed, give it 5% margin for safety 950km/h+ if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { - return SpeedLimitTextures[0]; + return SpeedLimitTextures["0" + suffix]; } var index = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? SpeedLimit.ToMphRounded(speedLimit) @@ -231,7 +238,7 @@ public static Texture2D GetSpeedLimitTexture(float speedLimit) { if (index > SpeedLimit.UPPER_KMPH) { Log.Info($"Trimming speed={speedLimit} index={index} to 140"); } - var trimIndex = Math.Min(SpeedLimit.UPPER_KMPH, Math.Max((ushort)0, index)); + var trimIndex = Math.Min(SpeedLimit.UPPER_KMPH, Math.Max((ushort)0, index)).ToString() + suffix; // Log._Debug($"texture for {speedLimit} is {index} trim={trimIndex}"); return SpeedLimitTextures[trimIndex]; } From eabcb18007993bda752335332b088e39bcca2c10 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Wed, 19 Jun 2019 21:28:06 -0600 Subject: [PATCH 063/142] Add More Languages --- TLM/TLM/Resources/lang.txt | 6 +++--- TLM/TLM/Resources/lang_de.txt | 5 ++++- TLM/TLM/Resources/lang_es.txt | 5 ++++- TLM/TLM/Resources/lang_fr.txt | 3 +++ TLM/TLM/Resources/lang_it.txt | 5 ++++- TLM/TLM/Resources/lang_ja.txt | 5 ++++- TLM/TLM/Resources/lang_ko.txt | 3 +++ TLM/TLM/Resources/lang_nl.txt | 5 ++++- TLM/TLM/Resources/lang_pl.txt | 5 ++++- TLM/TLM/Resources/lang_pt.txt | 5 ++++- TLM/TLM/Resources/lang_ru.txt | 5 ++++- TLM/TLM/Resources/lang_zh-tw.txt | 5 ++++- TLM/TLM/Resources/lang_zh.txt | 5 ++++- 13 files changed, 49 insertions(+), 13 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 88f95f215..6039b6109 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -233,6 +233,6 @@ Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict Speed_limit_unlimited No limit -Display_speed_limits_mph Display speed limits as mph instead of km/h -Miles_per_hour Miles per hour -Kilometers_per_hour Kilometers per hour +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index 63b2c749a..a338a1c5e 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Gilt auch für Links- & R Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 21457ec8a..1346824d1 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index b40634d4b..52dcab038 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -232,3 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Rechercher les mods incompatibles au Ignore_disabled_mods Ignorer les mods désactivés Traffic_Manager_detected_incompatible_mods Le gestionnaire de trafic a détecté des mods incompatibles Notify_me_if_there_is_an_unexpected_mod_conflict Afficher un message d'erreur si un mod incompatible est détecté +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index a4f86a030..98c681ee0 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index fdbd694c5..ff7ba497b 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets 一方通行路の左右 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index 1bc09409f..7c5e90990 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -232,3 +232,6 @@ Scan_for_known_incompatible_mods_on_startup 게임 실행시 비호환되는 모 Ignore_disabled_mods 비활성화된 모드 무시하기 Traffic_Manager_detected_incompatible_mods Traffic Manager와 비호환되는 모드 감지 Notify_me_if_there_is_an_unexpected_mod_conflict 모드와 비 호환되는 모드 발견 시 에러 보여주기 +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 5f748af47..1df756c13 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 9200d8b58..f3015aeba 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Uwzględnia również skr Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie gry Ignore_disabled_mods Ignoruj wyłączone mody Traffic_Manager_detected_incompatible_mods Traffic Manager wykrył niekompatybilne mody -Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index 1993850e4..445c23b06 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 4193e0a8c..9644deec7 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Разрешить нап Scan_for_known_incompatible_mods_on_startup Сканирование известных несовместимых модов при запуске Ignore_disabled_mods Игнорировать отключённые моды Traffic_Manager_detected_incompatible_mods Traffic Manager обнаружил несовместимые моды -Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index ff86907fa..d3c77e191 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & righ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index a65dc8ff7..7fd89c70a 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -231,4 +231,7 @@ Also_apply_to_left/right_turns_between_one-way_streets 单行道左右转也适 Scan_for_known_incompatible_mods_on_startup 启动时检测是否存在已知不兼容MOD Ignore_disabled_mods 忽略未启用MOD Traffic_Manager_detected_incompatible_mods 检测到的不兼容MOD -Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 +Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour From 57fe85169dc7adda5f4c6373c8d29c12914bc109 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Wed, 19 Jun 2019 21:36:59 -0600 Subject: [PATCH 064/142] Center More Things * Centered the text for the MPH/KMH * Removed unused method --- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 9c611f08a..1987d6e88 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -416,20 +416,15 @@ private void _guiSpeedLimitsWindow(int num) { DragWindow(ref windowRect); } - ///

Like addButton helper below, but adds unclickable space of the same size - private void _guiSpeedLimitsWindow_AddDummy() { - GUILayout.BeginVertical(); - var signSize = TrafficManagerTool.AdaptWidth(GuiSpeedSignSize); - GUILayout.Button( null as Texture, GUILayout.Width(signSize), GUILayout.Height(signSize)); - GUILayout.EndVertical(); - } - /// Helper to create speed limit sign + label below converted to the opposite unit /// Config value from GlobalConfig.I.M.ShowMPH /// The float speed to show private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added GUILayout.BeginVertical(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); var signSize = TrafficManagerTool.AdaptWidth(GuiSpeedSignSize); if (GUILayout.Button( TextureResources.GetSpeedLimitTexture(speedLimit), @@ -437,9 +432,17 @@ private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { showMph ? GUILayout.Height(signSize * 1.25f) : GUILayout.Height(signSize))) { currentPaletteSpeedLimit = speedLimit; } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + // For MPH setting display KM/H below, for KM/H setting display MPH + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); GUILayout.Label(showMph ? SpeedLimit.ToKmphPreciseString(speedLimit) : SpeedLimit.ToMphPreciseString(speedLimit)); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + GUILayout.EndVertical(); } From d95859db82ab1fca1bd6e3f7bfd53f056d83fb2f Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 20 Jun 2019 13:47:25 +0100 Subject: [PATCH 065/142] Remane 'Policies & Restrictions' tab to just 'Policies' Updated several lang files to remove the "& restrictions" text. Some languages (ja, ko, zh, zh-tw) not updated as I don't know how those languages work. The `Policies_&_Restrictions` translation key can be updated when we convert to xml, for now leave it as is. --- TLM/TLM/Resources/lang.txt | 10 +++++----- TLM/TLM/Resources/lang_de.txt | 18 +++++++++--------- TLM/TLM/Resources/lang_es.txt | 8 ++++---- TLM/TLM/Resources/lang_fr.txt | 10 +++++----- TLM/TLM/Resources/lang_it.txt | 14 +++++++------- TLM/TLM/Resources/lang_nl.txt | 8 ++++---- TLM/TLM/Resources/lang_pl.txt | 8 ++++---- TLM/TLM/Resources/lang_pt.txt | 24 ++++++++++++------------ TLM/TLM/Resources/lang_ru.txt | 8 ++++---- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 4851e3384..36ce596c5 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -123,7 +123,7 @@ Default_speed_limit Default speed limit Unit_system Unit system Metric Metric Imperial Imperial -Use_more_CPU_cores_for_route_calculation_if_available Use more CPU cores for route calculation (if available) +Use_more_CPU_cores_for_route_calculation_if_available Use more CPU cores for route calculation (if available) Activated_features Activated features Junction_restrictions Junction restrictions Prohibit_spawning_of_pocket_cars Prohibit cims to spawn pocket cars @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Evacuation buses may ignore traffic rules Evacuation_busses_may_only_be_used_to_reach_a_shelter Evacuation buses may only be used to reach a shelter Vehicle_behavior Vehicle behavior -Policies_&_Restrictions Policies & Restrictions +Policies_&_Restrictions Policies At_junctions At junctions In_case_of_emergency In case of emergency Show_lane-wise_speed_limits Show lane-wise speed limits @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, buses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index 5c5b70404..eb7569123 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -109,7 +109,7 @@ Rotate_left Links rotieren Rotate_right Rechts rotieren Name Name Apply Anwenden -Select_a_timed_traffic_light_program Wähle ein zeitgesteuertes Ampelprogramm aus +Select_a_timed_traffic_light_program Wähle ein zeitgesteuertes Ampelprogramm aus The_chosen_traffic_light_program_is_incompatible_to_this_junction Das ausgewählte Ampelprogramm ist inkompatibel zu dieser Kreuzung Advanced_AI_cannot_be_activated Erweiterte Fahrzeug-KI kann nicht aktiviert werden The_Advanced_Vehicle_AI_cannot_be_activated Die erweiterte Fahrzeug-KI kann nicht aktiviert werden, weil du bereits einen anderen Mod verwendest, der das Verhalten von Fahrzeugen verändern (z.B. Improved AI oder Traffic++). @@ -154,7 +154,7 @@ Individual_driving_styles Individuelle Fahrstile Evacuation_busses_may_ignore_traffic_rules Evakuierungsbusse dürfen Verkehrsregeln missachten Evacuation_busses_may_only_be_used_to_reach_a_shelter Evakuierungsbusse dienen nur zum Erreichen einer Notunterkunft Vehicle_behavior Fahrzeugverhalten -Policies_&_Restrictions Richtlinien & Einschränkungen +Policies_&_Restrictions Richtlinien At_junctions An Kreuzungen In_case_of_emergency Im Notfall Show_lane-wise_speed_limits Zeige spurweise Geschwindigkeitsbeschränkungen @@ -164,7 +164,7 @@ requires_game_restart erfordert einen Neustart des Spiels Customizations_come_into_effect_instantaneously Anpassungen treten sofort in Kraft Options Optionen Lock_main_menu_button_position Position der Hauptmenü-Schaltfläche sperren -Lock_main_menu_position Position des Hauptmenüs sperren +Lock_main_menu_position Position des Hauptmenüs sperren Recalculating_lane_routing Berechne Routenführung neu Please_wait Bitte warten Parking_restrictions Parkverbote @@ -189,11 +189,11 @@ TMPE_TUTORIAL_BODY_MainMenu Willkommen bei TM:PE!\n\nBenutzerhandbuch: http://ww TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Kreuzungsbeschränkungen TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Steuere, wie Fahrzeuge und Fußgänger sich an Kreuzungen zu verhalten haben.\n\n1. Klicke auf die Kreuzung, die du kontrollieren möchtest.\n2. Klicke auf das entsprechende Symbol, um die Beschränkungen ein- oder auszuschalten.\n\nVerfügbare Beschränkungen:\n- Erlaube/Verbiete Spurwechsel für Fahrzeuge, die geradeaus fahren.\n- Erlaube/Verbiete das Wenden.\n- Erlaube/Verbiete Fahrzeugen, in die blockierte Kreuzung einzufahren.\n- Erlaube/Verbiete Fußgängern, die Straße zu überqueren. TMPE_TUTORIAL_HEAD_LaneArrowTool Richtungspfeile -TMPE_TUTORIAL_BODY_LaneArrowTool Limitiere die Richtungen, in die Fahrzeuge fahren dürfen.\n\n1. Klicke auf ein Straßensegment nahe einer Kreuzung.\n2. Wähle die erlaubten Richtungen aus. +TMPE_TUTORIAL_BODY_LaneArrowTool Limitiere die Richtungen, in die Fahrzeuge fahren dürfen.\n\n1. Klicke auf ein Straßensegment nahe einer Kreuzung.\n2. Wähle die erlaubten Richtungen aus. TMPE_TUTORIAL_HEAD_LaneConnectorTool Fahrspurverbinder TMPE_TUTORIAL_BODY_LaneConnectorTool Verbinde zwei oder mehr Fahrspuren miteinander, um Fahrzeugen nur bestimmte Fahrspurwechsel zu erlauben.\n\n1. Klicke auf einen Spurwechselpunkt (grauer Kreis).\n2. Klicke auf eine Quellspurmarkierung.\n3. Klicke auf eine Zielspurmarkierung, um die Zielspur mit der Quellspur zu verbinden.\n4. Klicke irgendwo mit der sekundären Maustaste, um zur Qullspurselektion zurückzukehren.\n\nVerfügbare Tastenkombinationen:\n\n-Entf. oder Rücktaste: Lösche alle Fahrspurverbindungen\n- Umschalt + S: Wechsle durch alle verfügbaren "Bleib auf der Spur"-Vorlagen. TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manuelle Ampeln -TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Probiere die Ampelschaltungen von TM:PE aus.\n\n1. Klicke auf eine Kreuzung.\n2. Verwende das Werkzeug, um die Ampel zu kontrollieren. +TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Probiere die Ampelschaltungen von TM:PE aus.\n\n1. Klicke auf eine Kreuzung.\n2. Verwende das Werkzeug, um die Ampel zu kontrollieren. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parkverbote TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Steuere, wo das Parken erlaubt ist.\n\nKlicke auf die "P"-Symbole.\n\nVerfügbare Tastenkombinationen:\n\n- Umschalt: Halte die Taste beim Klicken, um Parkverbote auch auf benachbarte Straßensegmente anzuwenden. TMPE_TUTORIAL_HEAD_PrioritySignsTool Vorfahrtsschilder @@ -202,17 +202,17 @@ TMPE_TUTORIAL_HEAD_SpeedLimitsTool Geschwindigkeitsbeschränkungen TMPE_TUTORIAL_BODY_SpeedLimitsTool Setze Geschwindigkeitsbeschränkungen.\n\n1. Wähle im Fenster eine Geschwindigkeitsbegrenzung aus.\n2. Klicke auf ein Straßensegment, um die ausgewählte Begrenzung anzuwenden.\n\nVerfügbare Tastenkombinationen:\n\n- Umschalt: Halte die Taste beim Klicken, um Geschwindigkeitsbegrenzungen auch auf benachbarte Straßensegmente anzuwenden.\n- Strg: Halte die Taste beim Klicken, um Geschwindigkeitsbegrenzungen für einzelne Fahrspuren zu setzen. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Zeitgesteuerte Ampelschaltungen TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Definiere zeitgesteuerte Ampeln.\n\n1. Klicke auf eine Kreuzung.\n2. Klicke im Fenster auf "Schritt hinzufügen".\n3. Klick auf die Overlayelemente, um den gewünschten Zustand der Ampeln festzulegen.\n4. Klicke auf "Hinzufügen".\n5. Wiederhole die obigen Schritte bei Bedarf. -TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Ampeln setzen +TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Ampeln setzen TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Füge Ampeln hinzu oder lösche sie von Kreuzungen.\n\nKlicke auf eine Kreuzung, um ihre Ampel ein- oder auszuschalten. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Fahrzeugbeschränkungen TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Erlaube oder verbiete Fahrzeugen, bestimmte Straßensegmente zu benutzen.\n\n1. Klicke auf ein Straßensegment.\n2. Klicke auf die Symbole, um die Beschränkungen ein- oder auszuschalten.\n\nUnterschiedene Fahrzeugtypen:\n\n- Straßenfahrzeuge: KFZ, Busse, Taxen, Lieferwagen/Laster, Dienstleistungen, Notfallfahrzeuge\n- Züge: Passagierzüge, Frachtzüge\n\nVerfügbare Tastenkombinationen:\n\n- Umschalt: Halte die Taste beim Klicken, um Fahrzeugbeschränkungen auch auf benachbarte Straßen-/Schienensegmente anzuwenden. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Standard-Geschwindigkeitsbegrenzungen TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Benutze die Pfeiltasten in der oberen Hälfte des Fensters, um zwischen allen verfügbaren Straßensegmenten umzuschalten.\n2. Wähle auf die gleiche Weise in der unteren Hälfte die gewünschte Geschwindigkeitsbegrenzung aus.\n3. Klicke auf "Speichern" um die Auswahl für in der Zukunft gebaute Straßensegmente des gewählten Typs anzuwenden. Klicke auf "Speichern & Anwenden", um die Auswahl auch für bereits gebaute Straßen des gewählten Typs anzuwenden. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Füge einen neuen Zeitschritt hinzu -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Klick auf eine Ampel innerhalb des Overlays, um ihren Zustand zu ändern. Verwende die "Change mode"-Schaltfläche im Overlay, um Ampeln für verschiedene Richtungen hinzuzufügen.\n2. Gebe eine Minimal- und Maximaldauer für den Zeitschritt ein. Sobald die Minimalzeit verstrichen ist, wird die Ampelschaltung die ankommendenen Fahrzeuge messen und die Zahlen für alle Richtungen miteinander vergleichen.\n3. (optional) Konfiguriere die adaptive Schrittumschaltung. Im Standard wird der Schritt gewechselt, wenn (grob) weniger Fahrzeuge die Kreuzung durchqueren als Fahrzeuge warten.\n4. (optional) Konfiguriere die Sensitivität der Ampelschaltung. Ziehe den Regler z.B. nach links, um die Ampel weniger sensibel für wartende Fahrzeuge zu machen. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Füge einen neuen Zeitschritt hinzu +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Klick auf eine Ampel innerhalb des Overlays, um ihren Zustand zu ändern. Verwende die "Change mode"-Schaltfläche im Overlay, um Ampeln für verschiedene Richtungen hinzuzufügen.\n2. Gebe eine Minimal- und Maximaldauer für den Zeitschritt ein. Sobald die Minimalzeit verstrichen ist, wird die Ampelschaltung die ankommendenen Fahrzeuge messen und die Zahlen für alle Richtungen miteinander vergleichen.\n3. (optional) Konfiguriere die adaptive Schrittumschaltung. Im Standard wird der Schritt gewechselt, wenn (grob) weniger Fahrzeuge die Kreuzung durchqueren als Fahrzeuge warten.\n4. (optional) Konfiguriere die Sensitivität der Ampelschaltung. Ziehe den Regler z.B. nach links, um die Ampel weniger sensibel für wartende Fahrzeuge zu machen. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Kopiere eine zeitgesteuerte Ampel TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Klicke auf eine andere Kreuzung, um die Ampelschaltung dort einzufügen.\n\nKlicke irgendwo mit der sekundären Maustaste, um die Operation abzubrechen. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Füge der Ampelschaltung eine andere Kreuzung hinzu +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Füge der Ampelschaltung eine andere Kreuzung hinzu TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Klicke auf eine andere Kreuzung, um sie der Schaltung hinzuzufügen. Die Ampelschaltung wird danach beide Kreuzungen gleichzeitig steuern.\n\nKlicke irgendwo mit der sekundären Maustaste, um die Operation abzubrechen. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Entferne eine Kreuzung von der Ampelschaltung. TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Klicke auf eine der Kreuzungen, die momentan von der Ampelschaltungen kontrolliert werden. Die selektierte Ampel wird entfernt, so dass die Ampelschaltung die Kreuzung nicht mehr kontrolliert.\n\nKlicke irgendwo mit der sekundären Maustaste, um die Operation abzubrechen. diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index ef32ab98e..cabc145c4 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Los buses de evacuación pueden ignorar reglas de tránsito Evacuation_busses_may_only_be_used_to_reach_a_shelter Los buses de evacuación sólo pueden usarse para llegar a un refugio Vehicle_behavior Comportamiento vehícular -Policies_&_Restrictions Normas y restricciones +Policies_&_Restrictions Normas At_junctions En los cruces In_case_of_emergency En caso de emergencia Show_lane-wise_speed_limits Mostrar límite de velocidad en el carril @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index ff73e83f0..9764afd39 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -23,8 +23,8 @@ Delete Suppr. Timed_traffic_lights_manager Gestionnaire de feux tricolores chronométrés Add_step Ajouter un état Remove_timed_traffic_light Retirer le chronométrage -Min._Time: Temps Min : -Max._Time: Temps Max : +Min._Time: Temps Min : +Max._Time: Temps Max : Save Sauvegarder Add Ajouter Sensitivity Sensibilité @@ -62,7 +62,7 @@ TMPE_Title Gestionnaire de trafic : Édition Président Settings_are_defined_for_each_savegame_separately Paramètres définis pour chaque sauvegarde séparément Simulation_accuracy Précision de la simulation (une plus haute précision réduit les performances) Enable_highway_specific_lane_merging/splitting_rules Activer les règles spécifiques de divergence/convergence sur les autoroutes -Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Les conducteurs veulent changer de voies (seulement si l'IA avancée est activée) : +Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Les conducteurs veulent changer de voies (seulement si l'IA avancée est activée) : Maintenance Maintenance Very_often Très souvent Often Souvent @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Les bus d'évacuation peuvent ignorer les règles de trafic Evacuation_busses_may_only_be_used_to_reach_a_shelter Les bus d'évacuation ne peuvent être utilisés que pour atteindre un abri Vehicle_behavior Comportement des véhicules -Policies_&_Restrictions Restrictions et politiques +Policies_&_Restrictions Politiques At_junctions Aux intersections In_case_of_emergency En cas d'urgence Show_lane-wise_speed_limits Afficher les limites de vitesse par voie @@ -212,7 +212,7 @@ TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Ajouter une étape chronométr TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Pendant le jeu, cliquez sur les feux de circulation pour changer leur état. Utilisez le bouton "Changer de mode" pour ajouter des feux de circulation directionnels.\n2. Entrez à la fois une durée minimale et maximale pour l'étape. Après le temps minimum écoulé, le feu de circulation comptera et comparera les véhicules qui approchent.\n3. En option, sélectionnez un type de changement d'étape. Par défaut, l'étape change si à peu près moins de véhicules sont en train de rouler qu'en attente.\n4. En option, ajustez la sensibilité du feu. Par exemple, déplacez le curseur vers la gauche pour le rendre moins sensible aux véhicules en attente. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copier un feu de circulation chronométré TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Cliquez sur un autre carrefour pour coller le feu de circulation chronométré.\n\nCliquez n'importe où avec le bouton secondaire de la souris pour annuler l'opération. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Ajouter une intersection à un feu de circulation chronométré +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Ajouter une intersection à un feu de circulation chronométré TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Cliquez sur une autre intersection pour l'ajouter. Les deux feux seront associés de sorte que le programme chronométré contrôlera ensuite les deux intersections à la fois.\n\nCliquez n'importe où avec le bouton secondaire de la souris pour annuler l'opération. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Retirer une intersection d'un feu de circulation chronométré TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Cliquez sur l'une des intersections contrôlées par ce programme chronométré. Le feu sélectionné sera supprimé de telle sorte que le programme chronométré ne le gérera plus.\n\nCliquez n'importe où avec le bouton secondaire de la souris pour annuler l'opération. diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index 13a66e9dc..4c08401de 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -123,7 +123,7 @@ Default_speed_limit Limite velocità di default Unit_system Unità di misura Metric Metrico Imperial Imperiale -Use_more_CPU_cores_for_route_calculation_if_available Utilizza più core della CPU per il calcolo del percorso (se disponile) +Use_more_CPU_cores_for_route_calculation_if_available Utilizza più core della CPU per il calcolo del percorso (se disponile) Activated_features Features attivate Junction_restrictions Restrizioni incrocio Prohibit_spawning_of_pocket_cars Vieta ai "cims" di spawnare "pocket cars" @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules I bus di evacuazione possono ignorare le regole stradali Evacuation_busses_may_only_be_used_to_reach_a_shelter I bus di evacuazione possono essere usati solo per raggiungere un rifugio Vehicle_behavior Comportamente del veicolo -Policies_&_Restrictions Policies & Restrizioni +Policies_&_Restrictions Policies At_junctions Agli incroci In_case_of_emergency In caso di emergenza Show_lane-wise_speed_limits Mostra limite velocità per ogni corsia @@ -176,11 +176,11 @@ flow_ratio Indice flusso wait_ratio Indice attesa After_min._time_has_elapsed_switch_to_next_step_if Dopo min. tempo trascorso, passare al passo successivo se Adaptive_step_switching Azionamento adattivo prossimo stato -Dynamic_lane_section Selezione corsia dinamica +Dynamic_lane_section Selezione corsia dinamica Percentage_of_vehicles_performing_dynamic_lane_section Percentuale di veicoli che effettuano la selezione dinamica della corsia Vehicle_restrictions_aggression Rigorosità restrizioni veicoli Strict Rigoroso -Show_path-find_stats Mostra statistiche path-find +Show_path-find_stats Mostra statistiche path-find Remove_this_vehicle Rimuovi questo veicolo Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights I veicoli seguono le regole di precedenza agli incroci con semafori a tempo Enable_tutorial_messages Enable tutorial messages @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 4f610b89f..0e5ddd2ef 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -155,7 +155,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Evacuatiebussen mogen verkeersregels overtreden Evacuation_busses_may_only_be_used_to_reach_a_shelter Evacuatiebussen mogen enkel gebruikt worden om schuilkelders te bereiken Vehicle_behavior Vortuiggedrag -Policies_&_Restrictions Beleidsregels & beperkingen +Policies_&_Restrictions Beleidsregels At_junctions Bij splitsingen In_case_of_emergency In geval van nood Show_lane-wise_speed_limits Toon rijstrookspecifieke snelheidslimieten @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 4f740c718..7763228f3 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Autobusy ewakuacyjne mogą ignorować przepisy Evacuation_busses_may_only_be_used_to_reach_a_shelter Autobusy ewakuacyjne mogą przewozić tylko do schroniena Vehicle_behavior Zachowanie pojazdu -Policies_&_Restrictions Zasady i Ograniczenia +Policies_&_Restrictions Zasady At_junctions Na strzyżowaniach In_case_of_emergency W sytuacjach nadzwyczajnych Show_lane-wise_speed_limits Pokaż limity prędkości dla pasów @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Ograniczenia ruchu pojazdów TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Zabroń wjazdu pojazdom na konkretnych odcinkach drogi.\n\n1. Kliknij na odcinku drogi.\n2. Kliknij na wybranej ikonie, aby wł/wył ograniczenie.\n\nDostępne typy pojazdów:\n\n- Pojazdy drogowe: samochody, autobust, taxi, ciężarówki, pojazdy usługowe, pojazdy uprzywilejowane\n- Pojazdy szynowe: pociągi pasażerskie, towarowe\n\nSkróty klawiszowe:\n\n- Shift: Przytrzymaj Shift podczas klikania, aby zastosować ograniczenia do kilku odcinków drogi jednocześnie. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Domyślne limity prędkości TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Użyj strzałek u góry, aby przęłączać pomiędzy dostępnymi typami dróg.\n2. Poniżej, wybierz limit prędkości.\n3. Naciśnij "Zapisz", aby ustawić wybrany limit prędkości jako domyślny dla nowo wybudowanych dróg wybranego typu. Naciścij "Zapisz i zastosuj", aby zaktualizować wszystkie już istniejące drogi wybranego typu. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Dodaj krok -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. W ramach nakładki, kliknij na sygnalizatorze, aby zmienić jego stan. Użyj przycisku "Zmiana trybu", aby dodać kierunkowe sygnalizatory.\n2. Ustaw minimalny oraz maksymalny czas aktualnego kroku. Po upłynięciu minimalnego czasu sygnalizacja zacznie zliczać i porównywać ilość nadjeżdżających pojazdów.\n3. Opcjonalnie, wybierz tryb zmiany kroku. Domyślnie, krok zostaje zmieniony jeśli liczba poruszających się pojazdów jest dużo mniejsza niż oczekujących.\n4. Opcjonalnie, możesz dostosować czułość. Na przykład, przesuń suwak w lewo by zmniejszyć czułość na oczekujące pojazdy. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Dodaj krok +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. W ramach nakładki, kliknij na sygnalizatorze, aby zmienić jego stan. Użyj przycisku "Zmiana trybu", aby dodać kierunkowe sygnalizatory.\n2. Ustaw minimalny oraz maksymalny czas aktualnego kroku. Po upłynięciu minimalnego czasu sygnalizacja zacznie zliczać i porównywać ilość nadjeżdżających pojazdów.\n3. Opcjonalnie, wybierz tryb zmiany kroku. Domyślnie, krok zostaje zmieniony jeśli liczba poruszających się pojazdów jest dużo mniejsza niż oczekujących.\n4. Opcjonalnie, możesz dostosować czułość. Na przykład, przesuń suwak w lewo by zmniejszyć czułość na oczekujące pojazdy. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Kopiuj czasową sygnalizację TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Kliknij na innym skrzyżowaniu, aby wkleić skopiowane ustawienie.\n\nKliknij gdziekolwiek prawym przyciskiem myszy, aby anulować operację. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Dodaj skrzyżowanie do czasowej sygnalizacji +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Dodaj skrzyżowanie do czasowej sygnalizacji TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Kliknij na innym skrzyżowaniu, aby je dodać. Obydwa skrzyżowania zostaną połączone, ustawienia czasowej sygnalizacji będą działać na nich jednocześnie.\n\nKliknij gdziekolwiek prawym klawiszem myszy, aby anulować operację. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Usuń skrzyżowanie spod kontroli czasowej sygnalizacji TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Kliknij na jednym ze skrzyżować kontrolowanych przez czasową sygnalizację. Wybrane skrzyżowanie zostanie usunięte spod kontroli programu czasowej sygnalizacji.\n\nKliknij gdziekolwiek prawym klawiszem myszy, aby anulować operację. diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index df1f1abad..b8f555651 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -1,13 +1,13 @@ Switch_traffic_lights Interruptor de Semáforos -Add_priority_signs Placas de preferência +Add_priority_signs Placas de preferência Manual_traffic_lights Semáforo manual -Timed_traffic_lights Semáforos cronometrados +Timed_traffic_lights Semáforos cronometrados Change_lane_arrows Alterar setas de pista Clear_Traffic Limpar tráfego Disable_despawning Desativar despawning Enable_despawning Ativar despawning NODE_IS_LIGHT O cruzamento tem um semáforo.\nExclua o semáforo, escolhendo "Interruptor de Semáforos" e clicando em seu cruzamento. -NODE_IS_TIMED_LIGHT Esta junção é parte de um script cronometrado.\nPrimeiro selecione "Semáforos cronometrados", selecione este cruzamento e clique em remover. +NODE_IS_TIMED_LIGHT Esta junção é parte de um script cronometrado.\nPrimeiro selecione "Semáforos cronometrados", selecione este cruzamento e clique em remover. Select_nodes_windowTitle Selecione um cruzamento Select_nodes Selecione cruzamento(s) Node Cruzamento @@ -50,10 +50,10 @@ avg._wait Espera média min/max min/max Lane Pista Set_Speed Setar velocidade {0} -Max_speed Velocidade Máxima +Max_speed Velocidade Máxima Segment Segmento incoming entrando -Enable_Advanced_Vehicle_AI Ativar IA avançada de veículo +Enable_Advanced_Vehicle_AI Ativar IA avançada de veículo Vehicles_may_enter_blocked_junctions Veículos podem entrar em cruzamentos bloqueados All_vehicles_may_ignore_lane_arrows Todos os veículos podem ignorar setas da pista Busses_may_ignore_lane_arrows ônibus podem ignorar setas da pista @@ -74,9 +74,9 @@ Nodes_and_segments Cruzamentos e segmentos Lanes Faixas Path_Of_Evil_(10_%) Caminho do mal (10 %) Rush_Hour_(5_%) Hora do Rush (5 %) -Minor_Complaints_(2_%) Reclamações de menor importância (2 %) +Minor_Complaints_(2_%) Reclamações de menor importância (2 %) Holy_City_(0_%) Cidade Santa (0 %) -Forget_toggled_traffic_lights Esquecer os semáforos alternados +Forget_toggled_traffic_lights Esquecer os semáforos alternados Road_is_already_in_a_group! Pista já esta em um grupo! All_selected_roads_must_be_of_the_same_type! Todas as pistas selecionadas devem ser do mesmo tipo! Create_group Criar grupo @@ -90,7 +90,7 @@ Select_junction Selecionar cruzamento Cancel Cancelar Speed_limits Limite de Velocidade Persistently_visible_overlays Sobreposições persistentemente visíveis -Priority_signs Placas de preferência +Priority_signs Placas de preferência Vehicles_may_do_u-turns_at_junctions Veículos podem fazer retorno nos cruzamentos Vehicles_going_straight_may_change_lanes_at_junctions Veículos seguindo reto pode mudar de faixa nos cruzamentos Allow_u-turns Permitir retornos @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Os ônibus de evacuação podem ignorar as regras de trânsito Evacuation_busses_may_only_be_used_to_reach_a_shelter Os ônibus de evacuação só podem ser usados para alcançar um abrigo Vehicle_behavior Comportamento do veículo -Policies_&_Restrictions Políticas e Restrições +Policies_&_Restrictions Políticas At_junctions Nas junções In_case_of_emergency Em caso de emergência Show_lane-wise_speed_limits Mostrar limites de velocidade por faixa. @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index bbe68a1b4..232ce1090 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Автобусы эвакуации игнорируют правила движения Evacuation_busses_may_only_be_used_to_reach_a_shelter Эвакуационные автобусы, только чтобы добраться до аварийного убежища Vehicle_behavior Поведение транспорта -Policies_&_Restrictions Политики и Ограничения +Policies_&_Restrictions Политики At_junctions На перекрёстках In_case_of_emergency В случае крайней необходимости Show_lane-wise_speed_limits Показывать ограничения скорости по полосам @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Ограничение движени TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Разрешить или запретить типам транспорта использовать определённые сегменты дороги.\n\n1. Нажмите на сегмент дороги.\n2. Нажмите на значки, чтобы переключить ограничения.\n\nИспользуемые типы транспортных средств:\n\n- Дорожные транспортные средства: легковые автомобили, автобусы, такси, грузовые автомобили, служебные транспортные средства, аварийные транспортные средства\n- Железнодорожные транспортные средства: пассажирские поезда, грузовые поезда\n\nГорячая клавиша:\n\n- Shift: Удерживайте нажатой клавишу Shift, одновременно применяя ограничения для нескольких сегментов дороги. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Стандартные ограничения скорости TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Используйте клавиши со стрелками в верхней половине окна для переключения между всеми доступными сегментами дороги.\n2. В нижней половине выберите ограничение скорости.\n3. Нажмите "Сохранить", чтобы установить выбранный предел скорости по умолчанию для будущих сегментов дороги выбранного типа. Нажмите "Сохранить и применить", чтобы обновить все существующие дороги выбранного типа. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Добавить временной шаг -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Внутри игрового оверлея нажмите на светофоры, чтобы изменить их состояние. Используйте кнопку "Изменить режим", чтобы добавить стационарные светофоры.\n2. Введите как минимальную, так и максимальную продолжительность для шага. Очень скоро светофор начнёт считать и сравнивать приближающиеся транспортные средства.\n3. (опционально) Настройте адаптивное шаговое переключение. По умолчанию этот шаг изменяется когда, примерно, меньше транспортных средств пересекает перекрёсток, чем ожидающего транспорта.\n4. При необходимости отрегулируйте чувствительность переключения. Например, переместите ползунок влево, чтобы сделать настраиваемый светофор менее чувствительным к ожидающему транспорту. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Добавить временной шаг +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Внутри игрового оверлея нажмите на светофоры, чтобы изменить их состояние. Используйте кнопку "Изменить режим", чтобы добавить стационарные светофоры.\n2. Введите как минимальную, так и максимальную продолжительность для шага. Очень скоро светофор начнёт считать и сравнивать приближающиеся транспортные средства.\n3. (опционально) Настройте адаптивное шаговое переключение. По умолчанию этот шаг изменяется когда, примерно, меньше транспортных средств пересекает перекрёсток, чем ожидающего транспорта.\n4. При необходимости отрегулируйте чувствительность переключения. Например, переместите ползунок влево, чтобы сделать настраиваемый светофор менее чувствительным к ожидающему транспорту. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Скопируйте настраиваемый светофор TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Нажмите на другой перекрёсток, чтобы разместить настраиваемый светофор.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Добавить другой перекрёсток в шаблон светофора +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Добавить другой перекрёсток в шаблон светофора TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Нажмите на другой перекрёсток, чтобы добавить его. Программа с таймером будет контролировать светофоры на обоих перекрёстках одновременно.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Удалите перекрёсток из схемы настраиваемого светофора TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Нажмите на один из перекрёстков, которые контролируются этой программой времени. Выбранный светофор будет удален таким образом, что программа с таймером больше не будет управлять им.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. From 7f9ea6a50dda26bfefd42e5ed97c76cd2401d6d0 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Thu, 20 Jun 2019 19:05:52 +0200 Subject: [PATCH 066/142] New ESC readonly shortcut; Removed tool shortcuts by default; Shift+; for TM:PE --- TLM/TLM/Resources/lang.txt | 1 + TLM/TLM/Resources/lang_de.txt | 1 + TLM/TLM/Resources/lang_es.txt | 1 + TLM/TLM/Resources/lang_fr.txt | 1 + TLM/TLM/Resources/lang_it.txt | 1 + TLM/TLM/Resources/lang_ja.txt | 1 + TLM/TLM/Resources/lang_ko.txt | 1 + TLM/TLM/Resources/lang_nl.txt | 1 + TLM/TLM/Resources/lang_pl.txt | 1 + TLM/TLM/Resources/lang_pt.txt | 1 + TLM/TLM/Resources/lang_ru.txt | 1 + TLM/TLM/Resources/lang_zh-tw.txt | 1 + TLM/TLM/Resources/lang_zh.txt | 1 + TLM/TLM/State/KeyMapping.cs | 75 ++++++++++++++++++++++++-------- 14 files changed, 70 insertions(+), 18 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 36ce596c5..dc4c13856 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index eb7569123..6f2bdb9b8 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Werkzeug 'Kreuzungsbeschränkungen' verwe Keybind_use_speed_limits_tool Werkzeug 'Geschwindigkeitsbeschränkungen' verwenden Keybind_lane_connector_stay_in_lane Fahrspurverbinder: Bleib auf der Fahrspur Keybinds Tastenkombinationen +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index cabc145c4..7fd2fd6e0 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index 9764afd39..beaa8c2f1 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index 4c08401de..fb086db2e 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index 8a9de1b62..6161f0a17 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index c070f2d83..ee6e3a47b 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 0e5ddd2ef..9a774b30c 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 7763228f3..706c13a6a 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Narzędzie 'Ograniczenia na skrzyżowaniu Keybind_use_speed_limits_tool Narzędzie 'Limity prędkości' Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie Keybinds Skróty klawiszowe +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index b8f555651..3f1a1e602 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 232ce1090..836a915a1 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Инструмент 'Ограничен Keybind_use_speed_limits_tool Инструмент 'Ограничения скорости' Keybind_lane_connector_stay_in_lane Линии движения: Оставаться в своей полосе Keybinds Горячие клавиши +Keybind_Exit_subtool Закрыть инструмент и меню TM:PE diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index f8bb47bf1..ec3a08bd5 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index 48cfe4c2b..c025f53ca 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -241,3 +241,4 @@ Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs index a278e8c96..28b112087 100644 --- a/TLM/TLM/State/KeyMapping.cs +++ b/TLM/TLM/State/KeyMapping.cs @@ -14,6 +14,9 @@ namespace TrafficManager.State { public class OptionsKeymappingMain : OptionsKeymapping { private void Awake() { TryCreateConfig(); + AddReadOnlyKeymapping(Translation.GetString("Keybind_Exit_subtool"), + KeyToolCancel_ViewOnly); + AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), KeyToggleTMPEMainMenu); @@ -39,46 +42,55 @@ public class OptionsKeymapping : UICustomControl { protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; + /// + /// This input key can not be changed and is not checked, instead it is display only + /// + public static SavedInputKey KeyToolCancel_ViewOnly = + new SavedInputKey("keyExitSubtool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.Escape, false, false, false), + false); + public static SavedInputKey KeyToggleTMPEMainMenu = new SavedInputKey("keyToggleTMPEMainMenu", KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.T, true, false, true), + SavedInputKey.Encode(KeyCode.Semicolon, false, true, false), true); public static SavedInputKey KeyToggleTrafficLightTool = new SavedInputKey("keyToggleTrafficLightTool", KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.T, true, true, false), + SavedInputKey.Empty, true); public static SavedInputKey KeyLaneArrowTool = new SavedInputKey("keyLaneArrowTool", KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.A, true, true, false), + SavedInputKey.Empty, true); public static SavedInputKey KeyLaneConnectionsTool = new SavedInputKey("keyLaneConnectionsTool", KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.C, true, true, false), + SavedInputKey.Empty, true); public static SavedInputKey KeyPrioritySignsTool = new SavedInputKey("keyPrioritySignsTool", KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.P, true, true, false), + SavedInputKey.Empty, true); public static SavedInputKey KeyJunctionRestrictionsTool = new SavedInputKey("keyJunctionRestrictionsTool", KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.J, true, true, false), + SavedInputKey.Empty, true); public static SavedInputKey KeySpeedLimitsTool = new SavedInputKey("keySpeedLimitsTool", KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.S, true, true, false), + SavedInputKey.Empty, true); public static SavedInputKey KeyLaneConnectorStayInLane = @@ -111,23 +123,50 @@ protected void TryCreateConfig() { /// Text to display /// A SavedInputKey from GlobalConfig.KeyboardShortcuts protected void AddKeymapping(string label, SavedInputKey savedInputKey) { - var uiPanel = - component.AttachUIComponent(UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as - UIPanel; - if (count_++ % 2 == 1) uiPanel.backgroundSprite = null; + var uiPanel = component.AttachUIComponent( + UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; + if (count_++ % 2 == 1) { + uiPanel.backgroundSprite = null; + } // Create a label - var uILabel = uiPanel.Find("Name"); + var uiLabel = uiPanel.Find("Name"); // Create a button which displays the shortcut and modifies it on click - var uIButton = uiPanel.Find("Binding"); - uIButton.eventKeyDown += OnBindingKeyDown; - uIButton.eventMouseDown += OnBindingMouseDown; + var uiButton = uiPanel.Find("Binding"); + uiButton.eventKeyDown += OnBindingKeyDown; + uiButton.eventMouseDown += OnBindingMouseDown; // Set label text (as provided) and set button text from the SavedInputKey - uILabel.text = label; - uIButton.text = savedInputKey.ToLocalizedString("KEYNAME"); - uIButton.objectUserData = savedInputKey; + uiLabel.text = label; + uiButton.text = savedInputKey.ToLocalizedString("KEYNAME"); + uiButton.objectUserData = savedInputKey; + } + + /// + /// Creates a line of key mapping but does not allow changing it. + /// Used to improve awareness. + /// + /// + /// + protected void AddReadOnlyKeymapping(string label, SavedInputKey inputKey) { + var uiPanel = component.AttachUIComponent( + UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; + if (count_++ % 2 == 1) { + uiPanel.backgroundSprite = null; + } + + // Create a label + var uiLabel = uiPanel.Find("Name"); + + // Create a button which displays the shortcut and modifies it on click + var uiReadOnlyKey = uiPanel.Find("Binding"); + uiReadOnlyKey.Disable(); + + // Set label text (as provided) and set button text from the InputKey + uiLabel.text = label; + uiReadOnlyKey.text = inputKey.ToLocalizedString("KEYNAME"); + uiReadOnlyKey.objectUserData = inputKey; } protected void OnEnable() { From f6f04d1a4835ece1bf32eaf809180cbb74114199 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Thu, 20 Jun 2019 11:52:31 -0600 Subject: [PATCH 067/142] KM/H => km/h --- TLM/TLM/Resources/lang.txt | 2 +- TLM/TLM/Resources/lang_de.txt | 2 +- TLM/TLM/Resources/lang_es.txt | 2 +- TLM/TLM/Resources/lang_fr.txt | 2 +- TLM/TLM/Resources/lang_it.txt | 2 +- TLM/TLM/Resources/lang_ja.txt | 2 +- TLM/TLM/Resources/lang_ko.txt | 2 +- TLM/TLM/Resources/lang_nl.txt | 2 +- TLM/TLM/Resources/lang_pl.txt | 2 +- TLM/TLM/Resources/lang_pt.txt | 2 +- TLM/TLM/Resources/lang_ru.txt | 2 +- TLM/TLM/Resources/lang_zh-tw.txt | 2 +- TLM/TLM/Resources/lang_zh.txt | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 6039b6109..aeb3d3348 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -233,6 +233,6 @@ Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict Speed_limit_unlimited No limit -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index a338a1c5e..a4fdf4d8a 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 1346824d1..2bf197b63 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index 52dcab038..80a4c16b3 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Rechercher les mods incompatibles au Ignore_disabled_mods Ignorer les mods désactivés Traffic_Manager_detected_incompatible_mods Le gestionnaire de trafic a détecté des mods incompatibles Notify_me_if_there_is_an_unexpected_mod_conflict Afficher un message d'erreur si un mod incompatible est détecté -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index 98c681ee0..259816174 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index ff7ba497b..8e816bc06 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index 7c5e90990..78d4fe3ac 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup 게임 실행시 비호환되는 모 Ignore_disabled_mods 비활성화된 모드 무시하기 Traffic_Manager_detected_incompatible_mods Traffic Manager와 비호환되는 모드 감지 Notify_me_if_there_is_an_unexpected_mod_conflict 모드와 비 호환되는 모드 발견 시 에러 보여주기 -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 1df756c13..3b1b4e751 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index f3015aeba..789fbdc1b 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie Ignore_disabled_mods Ignoruj wyłączone mody Traffic_Manager_detected_incompatible_mods Traffic Manager wykrył niekompatybilne mody Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index 445c23b06..a38fabafb 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 9644deec7..f24ae01da 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Сканирование извес Ignore_disabled_mods Игнорировать отключённые моды Traffic_Manager_detected_incompatible_mods Traffic Manager обнаружил несовместимые моды Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index d3c77e191..aa3e530fb 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index 7fd89c70a..28b82fc41 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -232,6 +232,6 @@ Scan_for_known_incompatible_mods_on_startup 启动时检测是否存在已知不 Ignore_disabled_mods 忽略未启用MOD Traffic_Manager_detected_incompatible_mods 检测到的不兼容MOD Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 -Display_speed_limits_mph Display speed limits as MPH instead of KM/H +Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour From eb63347b9118ee5c39784e3f00442547ab24065b Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Thu, 20 Jun 2019 12:18:32 -0600 Subject: [PATCH 068/142] Refactor Textures * Moved all the textures to their own folder * Removed suffix from all the textures * Made UPPER_MPH public due to usage in TextureResources * Changed from int to string for SpeedLimitTextures * Made two dictionaries; one for Kmph, one for Mph * Completely rewrote GetSpeedLimitTexture to add MPH support and remove unnecessary if statements --- .../{0_kmh.png => speedlimits/kmh/0.png} | Bin .../{10_kmh.png => speedlimits/kmh/10.png} | Bin .../{100_kmh.png => speedlimits/kmh/100.png} | Bin .../{105_kmh.png => speedlimits/kmh/105.png} | Bin .../{110_kmh.png => speedlimits/kmh/110.png} | Bin .../{115_kmh.png => speedlimits/kmh/115.png} | Bin .../{120_kmh.png => speedlimits/kmh/120.png} | Bin .../{125_kmh.png => speedlimits/kmh/125.png} | Bin .../{130_kmh.png => speedlimits/kmh/130.png} | Bin .../{135_kmh.png => speedlimits/kmh/135.png} | Bin .../{140_kmh.png => speedlimits/kmh/140.png} | Bin .../{15_kmh.png => speedlimits/kmh/15.png} | Bin .../{20_kmh.png => speedlimits/kmh/20.png} | Bin .../{25_kmh.png => speedlimits/kmh/25.png} | Bin .../{30_kmh.png => speedlimits/kmh/30.png} | Bin .../{35_kmh.png => speedlimits/kmh/35.png} | Bin .../{40_kmh.png => speedlimits/kmh/40.png} | Bin .../{45_kmh.png => speedlimits/kmh/45.png} | Bin .../{5_kmh.png => speedlimits/kmh/5.png} | Bin .../{50_kmh.png => speedlimits/kmh/50.png} | Bin .../{55_kmh.png => speedlimits/kmh/55.png} | Bin .../{60_kmh.png => speedlimits/kmh/60.png} | Bin .../{65_kmh.png => speedlimits/kmh/65.png} | Bin .../{70_kmh.png => speedlimits/kmh/70.png} | Bin .../{75_kmh.png => speedlimits/kmh/75.png} | Bin .../{80_kmh.png => speedlimits/kmh/80.png} | Bin .../{85_kmh.png => speedlimits/kmh/85.png} | Bin .../{90_kmh.png => speedlimits/kmh/90.png} | Bin .../{95_kmh.png => speedlimits/kmh/95.png} | Bin .../{0_mph.png => speedlimits/mph/0.png} | Bin .../{10_mph.png => speedlimits/mph/10.png} | Bin .../{15_mph.png => speedlimits/mph/15.png} | Bin .../{20_mph.png => speedlimits/mph/20.png} | Bin .../{25_mph.png => speedlimits/mph/25.png} | Bin .../{30_mph.png => speedlimits/mph/30.png} | Bin .../{35_mph.png => speedlimits/mph/35.png} | Bin .../{40_mph.png => speedlimits/mph/40.png} | Bin .../{45_mph.png => speedlimits/mph/45.png} | Bin .../{5_mph.png => speedlimits/mph/5.png} | Bin .../{50_mph.png => speedlimits/mph/50.png} | Bin .../{55_mph.png => speedlimits/mph/55.png} | Bin .../{60_mph.png => speedlimits/mph/60.png} | Bin .../{65_mph.png => speedlimits/mph/65.png} | Bin .../{70_mph.png => speedlimits/mph/70.png} | Bin .../{75_mph.png => speedlimits/mph/75.png} | Bin .../{80_mph.png => speedlimits/mph/80.png} | Bin .../{85_mph.png => speedlimits/mph/85.png} | Bin .../{90_mph.png => speedlimits/mph/90.png} | Bin TLM/TLM/TLM.csproj | 96 +++++++++--------- TLM/TLM/Traffic/Data/SpeedLimit.cs | 2 +- TLM/TLM/UI/TextureResources.cs | 39 ++++--- 51 files changed, 68 insertions(+), 69 deletions(-) rename TLM/TLM/Resources/{0_kmh.png => speedlimits/kmh/0.png} (100%) rename TLM/TLM/Resources/{10_kmh.png => speedlimits/kmh/10.png} (100%) rename TLM/TLM/Resources/{100_kmh.png => speedlimits/kmh/100.png} (100%) rename TLM/TLM/Resources/{105_kmh.png => speedlimits/kmh/105.png} (100%) rename TLM/TLM/Resources/{110_kmh.png => speedlimits/kmh/110.png} (100%) rename TLM/TLM/Resources/{115_kmh.png => speedlimits/kmh/115.png} (100%) rename TLM/TLM/Resources/{120_kmh.png => speedlimits/kmh/120.png} (100%) rename TLM/TLM/Resources/{125_kmh.png => speedlimits/kmh/125.png} (100%) rename TLM/TLM/Resources/{130_kmh.png => speedlimits/kmh/130.png} (100%) rename TLM/TLM/Resources/{135_kmh.png => speedlimits/kmh/135.png} (100%) rename TLM/TLM/Resources/{140_kmh.png => speedlimits/kmh/140.png} (100%) rename TLM/TLM/Resources/{15_kmh.png => speedlimits/kmh/15.png} (100%) rename TLM/TLM/Resources/{20_kmh.png => speedlimits/kmh/20.png} (100%) rename TLM/TLM/Resources/{25_kmh.png => speedlimits/kmh/25.png} (100%) rename TLM/TLM/Resources/{30_kmh.png => speedlimits/kmh/30.png} (100%) rename TLM/TLM/Resources/{35_kmh.png => speedlimits/kmh/35.png} (100%) rename TLM/TLM/Resources/{40_kmh.png => speedlimits/kmh/40.png} (100%) rename TLM/TLM/Resources/{45_kmh.png => speedlimits/kmh/45.png} (100%) rename TLM/TLM/Resources/{5_kmh.png => speedlimits/kmh/5.png} (100%) rename TLM/TLM/Resources/{50_kmh.png => speedlimits/kmh/50.png} (100%) rename TLM/TLM/Resources/{55_kmh.png => speedlimits/kmh/55.png} (100%) rename TLM/TLM/Resources/{60_kmh.png => speedlimits/kmh/60.png} (100%) rename TLM/TLM/Resources/{65_kmh.png => speedlimits/kmh/65.png} (100%) rename TLM/TLM/Resources/{70_kmh.png => speedlimits/kmh/70.png} (100%) rename TLM/TLM/Resources/{75_kmh.png => speedlimits/kmh/75.png} (100%) rename TLM/TLM/Resources/{80_kmh.png => speedlimits/kmh/80.png} (100%) rename TLM/TLM/Resources/{85_kmh.png => speedlimits/kmh/85.png} (100%) rename TLM/TLM/Resources/{90_kmh.png => speedlimits/kmh/90.png} (100%) rename TLM/TLM/Resources/{95_kmh.png => speedlimits/kmh/95.png} (100%) rename TLM/TLM/Resources/{0_mph.png => speedlimits/mph/0.png} (100%) rename TLM/TLM/Resources/{10_mph.png => speedlimits/mph/10.png} (100%) rename TLM/TLM/Resources/{15_mph.png => speedlimits/mph/15.png} (100%) rename TLM/TLM/Resources/{20_mph.png => speedlimits/mph/20.png} (100%) rename TLM/TLM/Resources/{25_mph.png => speedlimits/mph/25.png} (100%) rename TLM/TLM/Resources/{30_mph.png => speedlimits/mph/30.png} (100%) rename TLM/TLM/Resources/{35_mph.png => speedlimits/mph/35.png} (100%) rename TLM/TLM/Resources/{40_mph.png => speedlimits/mph/40.png} (100%) rename TLM/TLM/Resources/{45_mph.png => speedlimits/mph/45.png} (100%) rename TLM/TLM/Resources/{5_mph.png => speedlimits/mph/5.png} (100%) rename TLM/TLM/Resources/{50_mph.png => speedlimits/mph/50.png} (100%) rename TLM/TLM/Resources/{55_mph.png => speedlimits/mph/55.png} (100%) rename TLM/TLM/Resources/{60_mph.png => speedlimits/mph/60.png} (100%) rename TLM/TLM/Resources/{65_mph.png => speedlimits/mph/65.png} (100%) rename TLM/TLM/Resources/{70_mph.png => speedlimits/mph/70.png} (100%) rename TLM/TLM/Resources/{75_mph.png => speedlimits/mph/75.png} (100%) rename TLM/TLM/Resources/{80_mph.png => speedlimits/mph/80.png} (100%) rename TLM/TLM/Resources/{85_mph.png => speedlimits/mph/85.png} (100%) rename TLM/TLM/Resources/{90_mph.png => speedlimits/mph/90.png} (100%) diff --git a/TLM/TLM/Resources/0_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/0.png similarity index 100% rename from TLM/TLM/Resources/0_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/0.png diff --git a/TLM/TLM/Resources/10_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/10.png similarity index 100% rename from TLM/TLM/Resources/10_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/10.png diff --git a/TLM/TLM/Resources/100_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/100.png similarity index 100% rename from TLM/TLM/Resources/100_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/100.png diff --git a/TLM/TLM/Resources/105_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/105.png similarity index 100% rename from TLM/TLM/Resources/105_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/105.png diff --git a/TLM/TLM/Resources/110_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/110.png similarity index 100% rename from TLM/TLM/Resources/110_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/110.png diff --git a/TLM/TLM/Resources/115_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/115.png similarity index 100% rename from TLM/TLM/Resources/115_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/115.png diff --git a/TLM/TLM/Resources/120_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/120.png similarity index 100% rename from TLM/TLM/Resources/120_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/120.png diff --git a/TLM/TLM/Resources/125_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/125.png similarity index 100% rename from TLM/TLM/Resources/125_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/125.png diff --git a/TLM/TLM/Resources/130_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/130.png similarity index 100% rename from TLM/TLM/Resources/130_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/130.png diff --git a/TLM/TLM/Resources/135_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/135.png similarity index 100% rename from TLM/TLM/Resources/135_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/135.png diff --git a/TLM/TLM/Resources/140_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/140.png similarity index 100% rename from TLM/TLM/Resources/140_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/140.png diff --git a/TLM/TLM/Resources/15_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/15.png similarity index 100% rename from TLM/TLM/Resources/15_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/15.png diff --git a/TLM/TLM/Resources/20_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/20.png similarity index 100% rename from TLM/TLM/Resources/20_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/20.png diff --git a/TLM/TLM/Resources/25_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/25.png similarity index 100% rename from TLM/TLM/Resources/25_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/25.png diff --git a/TLM/TLM/Resources/30_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/30.png similarity index 100% rename from TLM/TLM/Resources/30_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/30.png diff --git a/TLM/TLM/Resources/35_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/35.png similarity index 100% rename from TLM/TLM/Resources/35_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/35.png diff --git a/TLM/TLM/Resources/40_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/40.png similarity index 100% rename from TLM/TLM/Resources/40_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/40.png diff --git a/TLM/TLM/Resources/45_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/45.png similarity index 100% rename from TLM/TLM/Resources/45_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/45.png diff --git a/TLM/TLM/Resources/5_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/5.png similarity index 100% rename from TLM/TLM/Resources/5_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/5.png diff --git a/TLM/TLM/Resources/50_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/50.png similarity index 100% rename from TLM/TLM/Resources/50_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/50.png diff --git a/TLM/TLM/Resources/55_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/55.png similarity index 100% rename from TLM/TLM/Resources/55_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/55.png diff --git a/TLM/TLM/Resources/60_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/60.png similarity index 100% rename from TLM/TLM/Resources/60_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/60.png diff --git a/TLM/TLM/Resources/65_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/65.png similarity index 100% rename from TLM/TLM/Resources/65_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/65.png diff --git a/TLM/TLM/Resources/70_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/70.png similarity index 100% rename from TLM/TLM/Resources/70_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/70.png diff --git a/TLM/TLM/Resources/75_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/75.png similarity index 100% rename from TLM/TLM/Resources/75_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/75.png diff --git a/TLM/TLM/Resources/80_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/80.png similarity index 100% rename from TLM/TLM/Resources/80_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/80.png diff --git a/TLM/TLM/Resources/85_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/85.png similarity index 100% rename from TLM/TLM/Resources/85_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/85.png diff --git a/TLM/TLM/Resources/90_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/90.png similarity index 100% rename from TLM/TLM/Resources/90_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/90.png diff --git a/TLM/TLM/Resources/95_kmh.png b/TLM/TLM/Resources/speedlimits/kmh/95.png similarity index 100% rename from TLM/TLM/Resources/95_kmh.png rename to TLM/TLM/Resources/speedlimits/kmh/95.png diff --git a/TLM/TLM/Resources/0_mph.png b/TLM/TLM/Resources/speedlimits/mph/0.png similarity index 100% rename from TLM/TLM/Resources/0_mph.png rename to TLM/TLM/Resources/speedlimits/mph/0.png diff --git a/TLM/TLM/Resources/10_mph.png b/TLM/TLM/Resources/speedlimits/mph/10.png similarity index 100% rename from TLM/TLM/Resources/10_mph.png rename to TLM/TLM/Resources/speedlimits/mph/10.png diff --git a/TLM/TLM/Resources/15_mph.png b/TLM/TLM/Resources/speedlimits/mph/15.png similarity index 100% rename from TLM/TLM/Resources/15_mph.png rename to TLM/TLM/Resources/speedlimits/mph/15.png diff --git a/TLM/TLM/Resources/20_mph.png b/TLM/TLM/Resources/speedlimits/mph/20.png similarity index 100% rename from TLM/TLM/Resources/20_mph.png rename to TLM/TLM/Resources/speedlimits/mph/20.png diff --git a/TLM/TLM/Resources/25_mph.png b/TLM/TLM/Resources/speedlimits/mph/25.png similarity index 100% rename from TLM/TLM/Resources/25_mph.png rename to TLM/TLM/Resources/speedlimits/mph/25.png diff --git a/TLM/TLM/Resources/30_mph.png b/TLM/TLM/Resources/speedlimits/mph/30.png similarity index 100% rename from TLM/TLM/Resources/30_mph.png rename to TLM/TLM/Resources/speedlimits/mph/30.png diff --git a/TLM/TLM/Resources/35_mph.png b/TLM/TLM/Resources/speedlimits/mph/35.png similarity index 100% rename from TLM/TLM/Resources/35_mph.png rename to TLM/TLM/Resources/speedlimits/mph/35.png diff --git a/TLM/TLM/Resources/40_mph.png b/TLM/TLM/Resources/speedlimits/mph/40.png similarity index 100% rename from TLM/TLM/Resources/40_mph.png rename to TLM/TLM/Resources/speedlimits/mph/40.png diff --git a/TLM/TLM/Resources/45_mph.png b/TLM/TLM/Resources/speedlimits/mph/45.png similarity index 100% rename from TLM/TLM/Resources/45_mph.png rename to TLM/TLM/Resources/speedlimits/mph/45.png diff --git a/TLM/TLM/Resources/5_mph.png b/TLM/TLM/Resources/speedlimits/mph/5.png similarity index 100% rename from TLM/TLM/Resources/5_mph.png rename to TLM/TLM/Resources/speedlimits/mph/5.png diff --git a/TLM/TLM/Resources/50_mph.png b/TLM/TLM/Resources/speedlimits/mph/50.png similarity index 100% rename from TLM/TLM/Resources/50_mph.png rename to TLM/TLM/Resources/speedlimits/mph/50.png diff --git a/TLM/TLM/Resources/55_mph.png b/TLM/TLM/Resources/speedlimits/mph/55.png similarity index 100% rename from TLM/TLM/Resources/55_mph.png rename to TLM/TLM/Resources/speedlimits/mph/55.png diff --git a/TLM/TLM/Resources/60_mph.png b/TLM/TLM/Resources/speedlimits/mph/60.png similarity index 100% rename from TLM/TLM/Resources/60_mph.png rename to TLM/TLM/Resources/speedlimits/mph/60.png diff --git a/TLM/TLM/Resources/65_mph.png b/TLM/TLM/Resources/speedlimits/mph/65.png similarity index 100% rename from TLM/TLM/Resources/65_mph.png rename to TLM/TLM/Resources/speedlimits/mph/65.png diff --git a/TLM/TLM/Resources/70_mph.png b/TLM/TLM/Resources/speedlimits/mph/70.png similarity index 100% rename from TLM/TLM/Resources/70_mph.png rename to TLM/TLM/Resources/speedlimits/mph/70.png diff --git a/TLM/TLM/Resources/75_mph.png b/TLM/TLM/Resources/speedlimits/mph/75.png similarity index 100% rename from TLM/TLM/Resources/75_mph.png rename to TLM/TLM/Resources/speedlimits/mph/75.png diff --git a/TLM/TLM/Resources/80_mph.png b/TLM/TLM/Resources/speedlimits/mph/80.png similarity index 100% rename from TLM/TLM/Resources/80_mph.png rename to TLM/TLM/Resources/speedlimits/mph/80.png diff --git a/TLM/TLM/Resources/85_mph.png b/TLM/TLM/Resources/speedlimits/mph/85.png similarity index 100% rename from TLM/TLM/Resources/85_mph.png rename to TLM/TLM/Resources/speedlimits/mph/85.png diff --git a/TLM/TLM/Resources/90_mph.png b/TLM/TLM/Resources/speedlimits/mph/90.png similarity index 100% rename from TLM/TLM/Resources/90_mph.png rename to TLM/TLM/Resources/speedlimits/mph/90.png diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 371348a6a..8e20787ee 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -444,54 +444,54 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index 29b6ed254..169199929 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -24,7 +24,7 @@ public struct SpeedLimit { private const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph private const ushort LOWER_MPH = 5; - private const ushort UPPER_MPH = 90; + public const ushort UPPER_MPH = 90; private const ushort MPH_STEP = 5; public const int BREAK_PALETTE_COLUMN_MPH = 10; // palette shows M in a row, then break and another row diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index 4df8cd41e..cd871ee27 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -47,7 +47,8 @@ public class TextureResources { public static readonly Texture2D ClockPlayTexture2D; public static readonly Texture2D ClockPauseTexture2D; public static readonly Texture2D ClockTestTexture2D; - public static readonly IDictionary SpeedLimitTextures; + public static readonly IDictionary SpeedLimitTexturesKmph; + public static readonly IDictionary SpeedLimitTexturesMph; public static readonly IDictionary> VehicleRestrictionTextures; public static readonly IDictionary VehicleInfoSignTextures; public static readonly IDictionary ParkingRestrictionTextures; @@ -135,20 +136,19 @@ static TextureResources() { ClockTestTexture2D = LoadDllResource("clock_test.png", 512, 512); // TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor - SpeedLimitTextures = new TinyDictionary(); + SpeedLimitTexturesKmph = new TinyDictionary(); + SpeedLimitTexturesMph = new TinyDictionary(); // Load shared speed limit signs for Kmph and Mph // Assumes that signs from 0 to 140 with step 5 exist, 0 denotes no limit sign for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) { - var resource = LoadDllResource(speedLimit + "_kmh.png", 200, 200); - SpeedLimitTextures.Add(speedLimit + "_kmh", - resource ?? SpeedLimitTextures["5_kmh"]); + var resource = LoadDllResource($"SpeedLimits.Kmh.{speedLimit}.png", 200, 200); + SpeedLimitTexturesKmph.Add(speedLimit, resource ?? SpeedLimitTexturesKmph[5]); } // Signs from 0 to 90 for MPH for (var speedLimit = 0; speedLimit <= 90; speedLimit += 5) { - var resource = LoadDllResource(speedLimit + "_mph.png", 200, 200); - SpeedLimitTextures.Add(speedLimit + "_mph", - resource ?? SpeedLimitTextures["5_mph"]); + var resource = LoadDllResource($"SpeedLimits.Mph.{speedLimit}.png", 200, 250); + SpeedLimitTexturesMph.Add(speedLimit, resource ?? SpeedLimitTexturesMph[5]); } VehicleRestrictionTextures = new TinyDictionary>(); @@ -226,21 +226,20 @@ static TextureResources() { /// Ingame speed /// The texture, hopefully it existed public static Texture2D GetSpeedLimitTexture(float speedLimit) { - var suffix = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? "_mph" : "_kmh"; - // Special value for max speed, give it 5% margin for safety 950km/h+ + var mph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + var textures = mph ? SpeedLimitTexturesMph : SpeedLimitTexturesKmph; if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { - return SpeedLimitTextures["0" + suffix]; + return textures[0]; } - var index = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph - ? SpeedLimit.ToMphRounded(speedLimit) - : SpeedLimit.ToKmphRounded(speedLimit); - // Trim by kmph because UPPER_KMPH is the max road sign that we have - if (index > SpeedLimit.UPPER_KMPH) { - Log.Info($"Trimming speed={speedLimit} index={index} to 140"); + var index = mph ? SpeedLimit.ToMphRounded(speedLimit) : SpeedLimit.ToKmphRounded(speedLimit); + // Trim the index since 140 KMH / 90 MPH is the max sign we have + var upper = mph ? SpeedLimit.UPPER_MPH : SpeedLimit.UPPER_KMPH; + if (index > upper) { + Log.Info($"Trimming speed={speedLimit} index={index} to {upper}"); } - var trimIndex = Math.Min(SpeedLimit.UPPER_KMPH, Math.Max((ushort)0, index)).ToString() + suffix; - // Log._Debug($"texture for {speedLimit} is {index} trim={trimIndex}"); - return SpeedLimitTextures[trimIndex]; + var trimIndex = Math.Min(upper, Math.Max((ushort)0, index)); + Log._Debug($"Texture for {speedLimit} is {index} trim={trimIndex}"); + return textures[trimIndex]; } private static Texture2D LoadDllResource(string resourceName, int width, int height) From eb2192f1af31e6c2bc330f1932e75cb1859cec8d Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Thu, 20 Jun 2019 12:24:30 -0600 Subject: [PATCH 069/142] Slightly Reduce Sign Size * As per request from aubergine --- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 1987d6e88..55b196084 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -23,8 +23,8 @@ namespace TrafficManager.UI.SubTools { public class SpeedLimitsTool : SubTool { /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH - private const int GuiSpeedSignSize = 90; - private readonly float speedLimitSignSize = 80f; + private const int GuiSpeedSignSize = 80; + private readonly float speedLimitSignSize = 70f; private bool _cursorInSecondaryPanel; From fd9a2ee454dacd4e5eae27af9db4ea3543fc6a58 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Thu, 20 Jun 2019 12:30:38 -0600 Subject: [PATCH 070/142] Fix Folder Capitalization --- .../{speedlimits/kmh => SpeedLimits/Kmh}/0.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/10.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/100.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/105.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/110.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/115.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/120.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/125.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/130.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/135.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/140.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/15.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/20.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/25.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/30.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/35.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/40.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/45.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/5.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/50.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/55.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/60.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/65.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/70.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/75.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/80.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/85.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/90.png | Bin .../{speedlimits/kmh => SpeedLimits/Kmh}/95.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/0.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/10.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/15.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/20.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/25.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/30.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/35.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/40.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/45.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/5.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/50.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/55.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/60.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/65.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/70.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/75.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/80.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/85.png | Bin .../{speedlimits/mph => SpeedLimits/Mph}/90.png | Bin 48 files changed, 0 insertions(+), 0 deletions(-) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/0.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/10.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/100.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/105.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/110.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/115.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/120.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/125.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/130.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/135.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/140.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/15.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/20.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/25.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/30.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/35.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/40.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/45.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/5.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/50.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/55.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/60.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/65.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/70.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/75.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/80.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/85.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/90.png (100%) rename TLM/TLM/Resources/{speedlimits/kmh => SpeedLimits/Kmh}/95.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/0.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/10.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/15.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/20.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/25.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/30.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/35.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/40.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/45.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/5.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/50.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/55.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/60.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/65.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/70.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/75.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/80.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/85.png (100%) rename TLM/TLM/Resources/{speedlimits/mph => SpeedLimits/Mph}/90.png (100%) diff --git a/TLM/TLM/Resources/speedlimits/kmh/0.png b/TLM/TLM/Resources/SpeedLimits/Kmh/0.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/0.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/0.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/10.png b/TLM/TLM/Resources/SpeedLimits/Kmh/10.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/10.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/10.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/100.png b/TLM/TLM/Resources/SpeedLimits/Kmh/100.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/100.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/100.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/105.png b/TLM/TLM/Resources/SpeedLimits/Kmh/105.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/105.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/105.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/110.png b/TLM/TLM/Resources/SpeedLimits/Kmh/110.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/110.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/110.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/115.png b/TLM/TLM/Resources/SpeedLimits/Kmh/115.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/115.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/115.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/120.png b/TLM/TLM/Resources/SpeedLimits/Kmh/120.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/120.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/120.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/125.png b/TLM/TLM/Resources/SpeedLimits/Kmh/125.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/125.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/125.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/130.png b/TLM/TLM/Resources/SpeedLimits/Kmh/130.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/130.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/130.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/135.png b/TLM/TLM/Resources/SpeedLimits/Kmh/135.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/135.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/135.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/140.png b/TLM/TLM/Resources/SpeedLimits/Kmh/140.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/140.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/140.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/15.png b/TLM/TLM/Resources/SpeedLimits/Kmh/15.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/15.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/15.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/20.png b/TLM/TLM/Resources/SpeedLimits/Kmh/20.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/20.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/20.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/25.png b/TLM/TLM/Resources/SpeedLimits/Kmh/25.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/25.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/25.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/30.png b/TLM/TLM/Resources/SpeedLimits/Kmh/30.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/30.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/30.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/35.png b/TLM/TLM/Resources/SpeedLimits/Kmh/35.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/35.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/35.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/40.png b/TLM/TLM/Resources/SpeedLimits/Kmh/40.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/40.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/40.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/45.png b/TLM/TLM/Resources/SpeedLimits/Kmh/45.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/45.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/45.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/5.png b/TLM/TLM/Resources/SpeedLimits/Kmh/5.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/5.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/5.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/50.png b/TLM/TLM/Resources/SpeedLimits/Kmh/50.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/50.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/50.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/55.png b/TLM/TLM/Resources/SpeedLimits/Kmh/55.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/55.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/55.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/60.png b/TLM/TLM/Resources/SpeedLimits/Kmh/60.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/60.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/60.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/65.png b/TLM/TLM/Resources/SpeedLimits/Kmh/65.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/65.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/65.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/70.png b/TLM/TLM/Resources/SpeedLimits/Kmh/70.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/70.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/70.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/75.png b/TLM/TLM/Resources/SpeedLimits/Kmh/75.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/75.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/75.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/80.png b/TLM/TLM/Resources/SpeedLimits/Kmh/80.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/80.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/80.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/85.png b/TLM/TLM/Resources/SpeedLimits/Kmh/85.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/85.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/85.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/90.png b/TLM/TLM/Resources/SpeedLimits/Kmh/90.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/90.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/90.png diff --git a/TLM/TLM/Resources/speedlimits/kmh/95.png b/TLM/TLM/Resources/SpeedLimits/Kmh/95.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/kmh/95.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/95.png diff --git a/TLM/TLM/Resources/speedlimits/mph/0.png b/TLM/TLM/Resources/SpeedLimits/Mph/0.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/0.png rename to TLM/TLM/Resources/SpeedLimits/Mph/0.png diff --git a/TLM/TLM/Resources/speedlimits/mph/10.png b/TLM/TLM/Resources/SpeedLimits/Mph/10.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/10.png rename to TLM/TLM/Resources/SpeedLimits/Mph/10.png diff --git a/TLM/TLM/Resources/speedlimits/mph/15.png b/TLM/TLM/Resources/SpeedLimits/Mph/15.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/15.png rename to TLM/TLM/Resources/SpeedLimits/Mph/15.png diff --git a/TLM/TLM/Resources/speedlimits/mph/20.png b/TLM/TLM/Resources/SpeedLimits/Mph/20.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/20.png rename to TLM/TLM/Resources/SpeedLimits/Mph/20.png diff --git a/TLM/TLM/Resources/speedlimits/mph/25.png b/TLM/TLM/Resources/SpeedLimits/Mph/25.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/25.png rename to TLM/TLM/Resources/SpeedLimits/Mph/25.png diff --git a/TLM/TLM/Resources/speedlimits/mph/30.png b/TLM/TLM/Resources/SpeedLimits/Mph/30.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/30.png rename to TLM/TLM/Resources/SpeedLimits/Mph/30.png diff --git a/TLM/TLM/Resources/speedlimits/mph/35.png b/TLM/TLM/Resources/SpeedLimits/Mph/35.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/35.png rename to TLM/TLM/Resources/SpeedLimits/Mph/35.png diff --git a/TLM/TLM/Resources/speedlimits/mph/40.png b/TLM/TLM/Resources/SpeedLimits/Mph/40.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/40.png rename to TLM/TLM/Resources/SpeedLimits/Mph/40.png diff --git a/TLM/TLM/Resources/speedlimits/mph/45.png b/TLM/TLM/Resources/SpeedLimits/Mph/45.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/45.png rename to TLM/TLM/Resources/SpeedLimits/Mph/45.png diff --git a/TLM/TLM/Resources/speedlimits/mph/5.png b/TLM/TLM/Resources/SpeedLimits/Mph/5.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/5.png rename to TLM/TLM/Resources/SpeedLimits/Mph/5.png diff --git a/TLM/TLM/Resources/speedlimits/mph/50.png b/TLM/TLM/Resources/SpeedLimits/Mph/50.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/50.png rename to TLM/TLM/Resources/SpeedLimits/Mph/50.png diff --git a/TLM/TLM/Resources/speedlimits/mph/55.png b/TLM/TLM/Resources/SpeedLimits/Mph/55.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/55.png rename to TLM/TLM/Resources/SpeedLimits/Mph/55.png diff --git a/TLM/TLM/Resources/speedlimits/mph/60.png b/TLM/TLM/Resources/SpeedLimits/Mph/60.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/60.png rename to TLM/TLM/Resources/SpeedLimits/Mph/60.png diff --git a/TLM/TLM/Resources/speedlimits/mph/65.png b/TLM/TLM/Resources/SpeedLimits/Mph/65.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/65.png rename to TLM/TLM/Resources/SpeedLimits/Mph/65.png diff --git a/TLM/TLM/Resources/speedlimits/mph/70.png b/TLM/TLM/Resources/SpeedLimits/Mph/70.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/70.png rename to TLM/TLM/Resources/SpeedLimits/Mph/70.png diff --git a/TLM/TLM/Resources/speedlimits/mph/75.png b/TLM/TLM/Resources/SpeedLimits/Mph/75.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/75.png rename to TLM/TLM/Resources/SpeedLimits/Mph/75.png diff --git a/TLM/TLM/Resources/speedlimits/mph/80.png b/TLM/TLM/Resources/SpeedLimits/Mph/80.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/80.png rename to TLM/TLM/Resources/SpeedLimits/Mph/80.png diff --git a/TLM/TLM/Resources/speedlimits/mph/85.png b/TLM/TLM/Resources/SpeedLimits/Mph/85.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/85.png rename to TLM/TLM/Resources/SpeedLimits/Mph/85.png diff --git a/TLM/TLM/Resources/speedlimits/mph/90.png b/TLM/TLM/Resources/SpeedLimits/Mph/90.png similarity index 100% rename from TLM/TLM/Resources/speedlimits/mph/90.png rename to TLM/TLM/Resources/SpeedLimits/Mph/90.png From 56c6cf2684a6b8c0ed04b5cba7d610282fac4e18 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Thu, 20 Jun 2019 21:47:14 +0200 Subject: [PATCH 071/142] Shorten one phrase in PL translation --- TLM/TLM/Resources/lang_pl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 706c13a6a..20cbb3f57 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -63,7 +63,7 @@ Settings_are_defined_for_each_savegame_separately ustawienia są definiowane odd Simulation_accuracy Dokładność symulacji (większa dokładność zmiejsza wydajność gry) Enable_highway_specific_lane_merging/splitting_rules Aktywuj Zmienione zasady podziału/łączenia pasów dla autostrad Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Chęć kierowców do zmiany pasa (tylko gdy Zaawansowana SI jest włączona) -Maintenance Konserwacja (dodatkowe informacje) +Maintenance Konserwacja Very_often Bardzo często Often Często Sometimes Czasami From 67be383494657b57d7e74b0ee7516f799cc09270 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Thu, 20 Jun 2019 23:00:30 +0200 Subject: [PATCH 072/142] Style selector option; MPH can be toggled from the speeds palette --- TLM/TLM/Resources/lang.txt | 3 ++ TLM/TLM/State/ConfigData/Main.cs | 6 ++++ TLM/TLM/State/Options.cs | 39 +++++++++++++++++++++++--- TLM/TLM/Traffic/Data/SpeedLimit.cs | 5 ++++ TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 26 +++++++++++++---- TLM/TLM/UI/TextureResources.cs | 17 +++++++++-- 6 files changed, 84 insertions(+), 12 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index aeb3d3348..1a80efa18 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -236,3 +236,6 @@ Speed_limit_unlimited No limit Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US Square US white signs +theme_Round_UK Round UK signs diff --git a/TLM/TLM/State/ConfigData/Main.cs b/TLM/TLM/State/ConfigData/Main.cs index 579a4eb2e..53180e047 100644 --- a/TLM/TLM/State/ConfigData/Main.cs +++ b/TLM/TLM/State/ConfigData/Main.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using TrafficManager.Traffic.Data; using TrafficManager.UI.MainMenu; namespace TrafficManager.State.ConfigData { @@ -66,6 +67,11 @@ public class Main { ///
public bool DisplaySpeedLimitsMph = false; + /// + /// Selected theme for road signs when MPH is active. + /// + public MphSignStyle MphRoadSignStyle = MphSignStyle.SquareUS; + public void AddDisplayedTutorialMessage(string messageKey) { HashSet newMessages = DisplayedTutorialMessages != null ? new HashSet(DisplayedTutorialMessages) diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index dd75d4859..a5d2de9a7 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -14,6 +14,7 @@ using CSUtil.Commons; using System.Reflection; using TrafficManager.Manager.Impl; +using TrafficManager.Traffic.Data; namespace TrafficManager.State { @@ -32,6 +33,7 @@ public class Options : MonoBehaviour { private static UICheckBox scanForKnownIncompatibleModsToggle = null; private static UICheckBox ignoreDisabledModsToggle = null; private static UICheckBox displayMphToggle = null; + private static UIDropDown roadSignsMphThemeDropdown = null; private static UICheckBox individualDrivingStyleToggle = null; private static UIDropDown recklessDriversDropdown = null; private static UICheckBox relaxedBussesToggle = null; @@ -93,6 +95,7 @@ public class Options : MonoBehaviour { private static UIButton reloadGlobalConfBtn = null; private static UIButton resetGlobalConfBtn = null; + public static int roadSignMphStyleInt; public static bool instantEffects = true; public static int simAccuracy = 0; //public static int laneChangingRandomization = 2; @@ -236,11 +239,11 @@ public static void makeSettings(UIHelperBase helper) { scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; Indent(ignoreDisabledModsToggle); - displayMphToggle = generalGroup.AddCheckbox( - Translation.GetString("Display_speed_limits_mph"), - GlobalConfig.Instance.Main.DisplaySpeedLimitsMph, - onDisplayMphChanged) as UICheckBox; + // General: Speed Limits + setupSpeedLimitsPanel(panelHelper, generalGroup); + + // General: Simulation var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); simAccuracyDropdown = simGroup.AddDropdown(Translation.GetString("Simulation_accuracy") + ":", new string[] { Translation.GetString("Very_high"), Translation.GetString("High"), Translation.GetString("Medium"), Translation.GetString("Low"), Translation.GetString("Very_Low") }, simAccuracy, onSimAccuracyChanged) as UIDropDown; instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; @@ -489,6 +492,25 @@ public static void makeSettings(UIHelperBase helper) { tabStrip.selectedIndex = 0; } + private static void setupSpeedLimitsPanel(UIHelper panelHelper, UIHelperBase generalGroup) { + displayMphToggle = generalGroup.AddCheckbox( + Translation.GetString("Display_speed_limits_mph"), + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph, + onDisplayMphChanged) as UICheckBox; + + + var mphThemeOptions = new[] { + Translation.GetString("theme_Square_US"), + Translation.GetString("theme_Round_UK") + }; + roadSignMphStyleInt = (int)GlobalConfig.Instance.Main.MphRoadSignStyle; + roadSignsMphThemeDropdown = generalGroup.AddDropdown( + Translation.GetString("Road_signs_theme_mph") + ":", + mphThemeOptions, roadSignMphStyleInt, + onRoadSignsMphThemeChanged) as UIDropDown; + roadSignsMphThemeDropdown.width = 400; + } + private static void Indent(T component) where T : UIComponent { UILabel label = component.Find("Label"); if (label != null) { @@ -709,6 +731,15 @@ private static void onDisplayMphChanged(bool newValue) { GlobalConfig.WriteConfig(); } + private static void onRoadSignsMphThemeChanged(int newRoadSignStyle) { + if (!checkGameLoaded()) { + return; + } + var newStyle = newRoadSignStyle == 1 ? MphSignStyle.RoundUK : MphSignStyle.SquareUS; + Log._Debug($"Road Sign theme changed to {newStyle}"); + GlobalConfig.Instance.Main.MphRoadSignStyle = newStyle; + } + private static void onInstantEffectsChanged(bool newValue) { if (!checkGameLoaded()) return; diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index 169199929..7f19effa9 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -11,6 +11,11 @@ public enum SpeedUnit { Mph } + public enum MphSignStyle { + SquareUS = 0, + RoundUK = 1, + } + /// /// Defines a speed limit value with default Kmph and display value of Mph /// for when the option is set to display Mph. The engine still uses kmph. diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 55b196084..5aad14c52 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -34,7 +34,7 @@ public class SpeedLimitsTool : SubTool { private bool overlayHandleHovered; private Dictionary> segmentCenterByDir = new Dictionary>(); - private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * (GuiSpeedSignSize + 5), 225)); + private Rect paletteWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * (GuiSpeedSignSize + 5), 150)); private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); private HashSet currentlyVisibleSegmentIds; private bool defaultsWindowVisible = false; @@ -73,8 +73,8 @@ public override void OnToolGUI(Event e) { var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? Translation.GetString("Miles_per_hour") : Translation.GetString("Kilometers_per_hour")) + ")"; - windowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * (GuiSpeedSignSize + 5) : 8 * (GuiSpeedSignSize + 5); - windowRect = GUILayout.Window(254, windowRect, _guiSpeedLimitsWindow, + paletteWindowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * (GuiSpeedSignSize + 5) : 8 * (GuiSpeedSignSize + 5); + paletteWindowRect = GUILayout.Window(254, paletteWindowRect, _guiSpeedLimitsWindow, Translation.GetString("Speed_limits") + unitTitle, WindowStyle); if (defaultsWindowVisible) { @@ -83,7 +83,7 @@ public override void OnToolGUI(Event e) { Translation.GetString("Default_speed_limits"), WindowStyle); } - _cursorInSecondaryPanel = windowRect.Contains(Event.current.mousePosition) + _cursorInSecondaryPanel = paletteWindowRect.Contains(Event.current.mousePosition) || (defaultsWindowVisible && defaultsWindowRect.Contains(Event.current.mousePosition)); @@ -397,6 +397,9 @@ private void _guiSpeedLimitsWindow(int num) { GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); + //--------------------- + // UI buttons row + //--------------------- GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button(Translation.GetString("Default_speed_limits"), @@ -407,13 +410,26 @@ private void _guiSpeedLimitsWindow(int num) { GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); + //--------------------- + // Checkboxes row + //--------------------- GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); showLimitsPerLane = GUILayout.Toggle(showLimitsPerLane, Translation.GetString("Show_lane-wise_speed_limits")); + GUILayout.FlexibleSpace(); + + // Display MPH checkbox, if ticked will save global config + var displayMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + displayMph = GUILayout.Toggle(displayMph, Translation.GetString("Display_speed_limits_mph")); + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph != displayMph) { + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph = displayMph; + GlobalConfig.WriteConfig(); + } + GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); - DragWindow(ref windowRect); + DragWindow(ref paletteWindowRect); } /// Helper to create speed limit sign + label below converted to the opposite unit diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index cd871ee27..0b94897b8 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -226,19 +226,30 @@ static TextureResources() { /// Ingame speed /// The texture, hopefully it existed public static Texture2D GetSpeedLimitTexture(float speedLimit) { - var mph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + var m = GlobalConfig.Instance.Main; + var unit = m.DisplaySpeedLimitsMph ? SpeedUnit.Mph : SpeedUnit.Kmph; + return GetSpeedLimitTexture(speedLimit, m.MphRoadSignStyle, unit); + } + + public static Texture2D GetSpeedLimitTexture(float speedLimit, MphSignStyle mphStyle, SpeedUnit unit) { + // Select the source for the textures based on unit and the theme + var mph = unit == SpeedUnit.Mph; var textures = mph ? SpeedLimitTexturesMph : SpeedLimitTexturesKmph; + + // Trim the range if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { return textures[0]; } + + // Round to nearest 5 MPH or nearest 10 km/h var index = mph ? SpeedLimit.ToMphRounded(speedLimit) : SpeedLimit.ToKmphRounded(speedLimit); - // Trim the index since 140 KMH / 90 MPH is the max sign we have + + // Trim the index since 140 km/h / 90 MPH is the max sign we have var upper = mph ? SpeedLimit.UPPER_MPH : SpeedLimit.UPPER_KMPH; if (index > upper) { Log.Info($"Trimming speed={speedLimit} index={index} to {upper}"); } var trimIndex = Math.Min(upper, Math.Max((ushort)0, index)); - Log._Debug($"Texture for {speedLimit} is {index} trim={trimIndex}"); return textures[trimIndex]; } From fd2675bd58180efbb055e88af7b12b1755857265 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Fri, 21 Jun 2019 00:17:07 +0200 Subject: [PATCH 073/142] British signs added, style selector for MPH added, translations --- TLM/TLM/Resources/SpeedLimits/Mph_UK/0.png | Bin 0 -> 6271 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/10.png | Bin 0 -> 11983 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/15.png | Bin 0 -> 11053 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/20.png | Bin 0 -> 12103 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/25.png | Bin 0 -> 11700 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/30.png | Bin 0 -> 12266 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/35.png | Bin 0 -> 11962 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/40.png | Bin 0 -> 11767 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/45.png | Bin 0 -> 11428 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/5.png | Bin 0 -> 10605 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/50.png | Bin 0 -> 12086 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/55.png | Bin 0 -> 11772 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/60.png | Bin 0 -> 12278 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/65.png | Bin 0 -> 12057 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/70.png | Bin 0 -> 11668 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/75.png | Bin 0 -> 11360 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/80.png | Bin 0 -> 12321 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/85.png | Bin 0 -> 11870 bytes TLM/TLM/Resources/SpeedLimits/Mph_UK/90.png | Bin 0 -> 12163 bytes .../SpeedLimits/{Mph => Mph_US}/0.png | Bin .../SpeedLimits/{Mph => Mph_US}/10.png | Bin .../SpeedLimits/{Mph => Mph_US}/15.png | Bin .../SpeedLimits/{Mph => Mph_US}/20.png | Bin .../SpeedLimits/{Mph => Mph_US}/25.png | Bin .../SpeedLimits/{Mph => Mph_US}/30.png | Bin .../SpeedLimits/{Mph => Mph_US}/35.png | Bin .../SpeedLimits/{Mph => Mph_US}/40.png | Bin .../SpeedLimits/{Mph => Mph_US}/45.png | Bin .../SpeedLimits/{Mph => Mph_US}/5.png | Bin .../SpeedLimits/{Mph => Mph_US}/50.png | Bin .../SpeedLimits/{Mph => Mph_US}/55.png | Bin .../SpeedLimits/{Mph => Mph_US}/60.png | Bin .../SpeedLimits/{Mph => Mph_US}/65.png | Bin .../SpeedLimits/{Mph => Mph_US}/70.png | Bin .../SpeedLimits/{Mph => Mph_US}/75.png | Bin .../SpeedLimits/{Mph => Mph_US}/80.png | Bin .../SpeedLimits/{Mph => Mph_US}/85.png | Bin .../SpeedLimits/{Mph => Mph_US}/90.png | Bin TLM/TLM/Resources/lang.txt | 5 +- TLM/TLM/Resources/lang_de.txt | 10 ++- TLM/TLM/Resources/lang_es.txt | 4 ++ TLM/TLM/Resources/lang_fr.txt | 4 ++ TLM/TLM/Resources/lang_it.txt | 4 ++ TLM/TLM/Resources/lang_ja.txt | 4 ++ TLM/TLM/Resources/lang_ko.txt | 4 ++ TLM/TLM/Resources/lang_nl.txt | 4 ++ TLM/TLM/Resources/lang_pl.txt | 4 ++ TLM/TLM/Resources/lang_pt.txt | 4 ++ TLM/TLM/Resources/lang_ru.txt | 10 ++- TLM/TLM/Resources/lang_zh-tw.txt | 4 ++ TLM/TLM/Resources/lang_zh.txt | 4 ++ TLM/TLM/State/Options.cs | 20 ++++-- TLM/TLM/TLM.csproj | 59 ++++++++++++------ TLM/TLM/Traffic/Data/SpeedLimit.cs | 12 ++++ TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 12 ++-- TLM/TLM/UI/TextureResources.cs | 36 +++++++++-- 56 files changed, 164 insertions(+), 40 deletions(-) create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/0.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/10.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/15.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/20.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/25.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/30.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/35.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/40.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/45.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/5.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/50.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/55.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/60.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/65.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/70.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/75.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/80.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/85.png create mode 100644 TLM/TLM/Resources/SpeedLimits/Mph_UK/90.png rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/0.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/10.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/15.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/20.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/25.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/30.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/35.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/40.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/45.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/5.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/50.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/55.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/60.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/65.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/70.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/75.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/80.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/85.png (100%) rename TLM/TLM/Resources/SpeedLimits/{Mph => Mph_US}/90.png (100%) diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/0.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/0.png new file mode 100644 index 0000000000000000000000000000000000000000..f90cdb1c09b6d350304b079b86c5827925dc970b GIT binary patch literal 6271 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/10.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/10.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f9c445aebdac0bb18367e81ff1608ac0d9eaf1 GIT binary patch literal 11983 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%0A0KK(bSFP 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 b9MG*#+q(Fzo*L4}`axb=MXE-^H2D7k*^*)B literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/20.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/20.png new file mode 100644 index 0000000000000000000000000000000000000000..52170aa4b1fd3a59116d0ab999f3833eacd68211 GIT binary patch literal 12103 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/25.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/25.png new file mode 100644 index 0000000000000000000000000000000000000000..d5d0832054c23e89f40d26a309634731041a57bb GIT binary patch literal 11700 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 CD(UC| literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/30.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/30.png new file mode 100644 index 0000000000000000000000000000000000000000..384c47b9550b397253186aa21a7b1f23188e8487 GIT binary patch literal 12266 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 XG*}D23|oV_dk7f`Me%A;qu~DoSu))6 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/35.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/35.png new file mode 100644 index 0000000000000000000000000000000000000000..4080dddcd349e93618062cf67fdd89f74b838f3f GIT binary patch literal 11962 zcmWk!V{{x_0FD|o&Ze=gjT@`68#lJi#%gTaw(Z8**tTspdi#FdnX_{)&dywX3R94m zK>mvN6#@bRSxQn=349;R+29+D*$=rN5D;}Sh;Ig8z`qgfB{iJD^%wpx zko=44H{geG&Qfw>aNCe*nCy6`(UT0|M>x)6>dwk`rp~SgjwTTH1{Su?jJ77uBQG{VMdzW zWMj=hYAh-S0N~mvhrh)4YZQ5opt@yP^J0!RC8}QCHCn#@g4m+&gNLS9iH~bWX_1q0 z^XZ3nXOF9O<|pz$RkY9HtLqlhd$#9u_=pGi_sf|3t&P3P$Ai7fXk6usYS*Lo1Uul! z69d5T<~K^G872DKE%jpHB|K3| zVYTU&VdNP-celb<^0dRWF4s^NStqLvpo$!fme0cY<*DpBZr>^qB~^cMLX52kR8pT5 zaBwhIiI0+XZ3Stkq%hnS$0dPIjBOm1>-nqF`uK5ypv`gXTcZZ$kr^x-%-zpt!jB=r zkgM$uH`weTv)vUM5fu)z&*#8X7OBT87QbI5g?hN=b3=XQMty+-`oZc6=`$w9b8P0F z3w0rMT5{fB&KC+1a8qL?k2ACswP|jvi-99d6I$=p*Wv(^JlRBKToOd%Kh_TiglIY2 zC1YLQZ75SC$eynP+NLB^$Q3|KtWLWh@qdr*H5hQ;WHLJLp{A$;VLSzw<^lgMSZ592P1^h4hxSu(oDdcj>L$-zX8c)#8S~2BjshC* zlIHS)`IJj3SD|eFG?%TZHC~jLaIXV;tY`Yh(icP&WX1L z&dN06uAMm@pj?6xYw1fLpi6->F z{Tfvx1S!8x0QbnrI*m1sOQt3o8c%7=7;W-TK<5DU@hv40KmumPFGaI7B;pxrmn%g9 zX-S5hL;Lp#mjZ+pKbvW;3L_$4wqACq6pj2z7F_PYAQn!L#8yaWy>Fk=RJ`AZ%O z7TTDeDDXGlKRPV=$~$1ZkM8}&lWEhpktrg%ErH2!!%7$xt3x?wcmjx*Ty2JbNX6Gd z$h}%l&b`{M47~YNUX2jM9}G00;0zz&U*P-YNPJf)L%#jOF|nfyN>K>_0K-$VN_WVU z+--Oh{zNd6i#@IXl+N}|OMG|@Rf)`zEXQJ?>IP~_O?1Bx__D{qgojJz7AC?-k6DI& zVq*h*b&1MWKFM!vHpI)ZfTZ650@Me@C=^9Z-NNA?FLp}ypl=j48z+7B>kOI<{Tv_P(7t` z>*H~vgjADQL+PAM^9_N*?b(Vz7ewqcrZ&f{>>XJ`S$qA0}Ri73_sg<=WjI9(s z7Mlp4`SX9ti4T`Dw^Kc$>PHFy?H`((aeWMkj&EBd_-1juV#}eVCTH@6 z&vC<254TC9jzztH7LgkaKnYVca9{hH;Ym~6lzfRP609e6D0q3p%B1!Nrqa@y5!8mC z%+bb)=sPX?f)T&nb?69>Mgv@9Zb=2Aji#=yvCk^0KyiZS> zPAH|evoq%Ik^>jX&EcQLgn~SA7ys?SKOE&p_K!J&t((SYIz~`D>Ws97- zSZ=OgV&}DV)q84b6*9>m_q#M0PmE6UG|lfwB%46Nsy^`48>3?=DgRYv0A7JuUKuU1 zf5xh@n%mF^5RF05!I{qyRyiZ#f4w?-|4$KF3YRq z3%7&Zb2s^|?k0F@vQGY`9P$JlG9x_2{QsjG~GRwmbVhSg$^hT_1K`>Z`8Z zswUdh<`vz42U#Oq$_N=j>=OwyI;OFd2LDaJSnu}!Zq(=JJQHi!nCLNP>e+py;VG6* z>dEz6#LtjCW@3=lhSW_Hi9f^{D_}?HU>=wBMsdfK)(vvX(Idye5}VLZ5ROwL1UhRw8~$`P@qEA2TV zTzx4u6!UL$#c^~;pf^>-G|-j~wWOl-S~kyG$y2I^IR+UH`n_~p*CxHpI{Q`YAAA-p(1l}~6d z&t^&Gk0OknmvF}qdSd>LjJo~v0|AjD%08FO-=y{rM`>8_m()r8CTf31VQK>+Vzr(} z+TTk@XfVUp;)A0=VvgDMSWbn+2SSo_r>;Q8+k&k$lqu`t>byZbS4}gz!1$d0Qkt`( znl2NR=wPJP7Rsqkl5@|lK>6F0r^CkayVTWSl#z27Nc#j@3;Hz83-chVB$)qc#V6YsTd7zp8hZw(P?y7Ty4TDhZu^iNB;M| zjDHx)dPe@%^QSdqQ`}#2(Ogts zDDWTW3|HQIoTR|YpXl3xeC3PFG})*q$oPA5TJo=KyORahe}$ZsH%I*W=*TvvMSM7+ zHgvXSZTjam1co#(#uU)v*}`FF`u|wjQ9-iFs}@M)R%(Yi)6Syv@4K&4uV&(>@{A2~ z*fktVW&{ZrQlCuOO1!SZ7vR^fk~nM!KHR;ItaWN?Cc0 z1do@9jICRB=Ka$^m4is&-=RjMIr__WKPqVMr=Xl|*Mr(@lhv=?2M>fQGAUVE3fq

qWzH%KM7J3`^CS>a>$O0T@VHD%WJMa!ODUE?pIw;BdWi2#4s>068&>i+^}HBpSFCbq{H}RGdQBW zZb8(FgSuTFaoPNy^>~>C3-rGbU8Lzx&hum;zK3CXv_)4rUcK~UZ|9G*6h`%YB?fdbv-!LU$q!dV8)AP};FlnBP zrvlVwmcY}mAYNg z3kt-R=Ft2P^UA~Qj$A8wB5=2Z*m}$e{m|p)WGngpmWnLDI~9f-W3^uTGy`D(Q4Zl7 z@5Ek^GbDu@-i^FEqg5}(av73q%{D<%1PGbTq+Z2rK&@uXQA}i&2q7t2(3yR)`VxDr zCjo3y=+PX-nunKL@W244$%tGvV6DZDIxQ`&4>p>@=if<*?CvxfBcnXcPj&@F>CdNN zg{4OO=&=fM?Un<#q0n%&x{m+44z0^(rL4D@+S8A?aPjGIDF5o*pU+>7?PD8x1 z#1YI01FyXwn*MJr;gQTL1Uo>jqU>*2CTm#v6T}A~PIUK3`b?pBeo4F9=tya_TsTb? zk8-wMsSjHC>n58cphF|ls6hXL8*$ug`8MsPxNPp%_!ltR3Ejt!nT{^YxQ8e4*@ z1b%hbqozTDI1f|nHk2Cl-c27O6TC?%mZjLswt;ZgoUo4t*VxHR<|xIGTZIv-0DDR` zNECHO*f;O-&A-*6$WRQJ*P8binRA_lK~S^w+Xu9u6e0k|Q{-46pIIp_`pd~8FwfWP z&o0h`s)y5#$UN)kEWL*6ywS;QQQuG`hwXZ6+`%m8s67QkBru?dd~1)oE9Io2a|tkW#VI~fU=Rh0YT<+9^y#2*@N zWT!tw>cYoLiZ;33!ah%xWdV#iFagH_0p zARAYuT5u0M6GFxS1064i89`GjW1d%CDg3GeSd7{< z3~cHgcAJ^&KCc)1HNNlkZFiHrB9mISZ9xm7Vwx9g8o>Fho_c9Nk!Pb4fxt0r`lht_#bD;bl?Al$ z<4V)_b{}*uH6%1u<>TSO;~CEP7gbC}t#%-CLqXwfx+c_p*~ph9seAJ*#HOHWaw>+Q8bJ%d|QYD+?=hbUP*~wiEE3o?5nilsCH(u^1HO z6FweleB*rI_K5e>t*S{^R#v3pK)T(b_8TrsgatU3G_P_IztNHaNIPjDe~s7|EV}i0$Ltxv8%J$x(*-{L@DvDaQHZ22ny!(t8b{w%! zEHPg!G(wsvxDy;i9Dq^0wWpCd^~&QOoCc{lJ(btwK$QHp8=yp_e(A8|_k|(lNN=H3 zLg0ZYkAne^?}wKfVq2*u3H2%2knjaW9q|y-Exk{yim!>3IUEj2zWmoO4w}ZI(Q)8Ot*Nfde$%{WYt$46{aMo^ z-NsPKCgjD!hjNu2ncRn3QKWaP(Sx*|uQ>lBZ+p8pAPv(IeIif%OzH3(kA@~Oo&(Pa z(kS0o+l%B7I(gUK&b0Zuzg#j15oe#f1g2&_^iAH(ie!fik)+;&ZuKGb5GS=;*-Co*3e zW9rdfIiU zni1*7P~X8fYE~(A;n(%b%gbXW7X^>l)xdq|#fGp|ZUnSAxY< z`Ox);AfJ9Mr|qa9`|O)#I>1UvODl~(nLx9|h&}ugag#j#F|p|lUg$CcLa~B6-F3UW z*QTD|NHd{f23RbNAYgM#KYB43=RF{Jh2rGPO$dz!$xaS0lK@dsaz$Tuh)DElh3#)GIq~0@y>!Pz4Qk#j(oE!NvgF?l~EhvJU+2ljXOkp-Z3J`g)Uew#hTH? zg4z6aJHmFcS8rYyOE8o_1?c}D;s-v}Y9^u))tXM%bwFjU;1_JYp#dB9=>${1Ir!l+ z$^JxI zp-eIZ`XayjU)T?RG0=_B@=CkNLuA6m!ILP~~)iy|;BJ&UeeiTIB$f z3=W&+1UyE2c(QWO+WGV;^c`E1V4^Rm>~;%0rqZ&q>mfG?;ca1++Y3qDL&nPG5%u+~ zMQpM4a#zUKwbL24LRYkM?D|8IxP9$#GF6kx3l(aJE{eby1YP9F_{@*o%3pR=%>*O~ z^mrcur~)LPPrjcLlVGZ1gojlIAq>e+yNhO0n+L7do!<^OCK7^DM_nfJGL8QKaqc6< z)n^FIyPY=#dm_j^i7xY6&PRf&%cr$I?;fnKm)e6z+gHAyylwmOGJiPAh`8)YJj3h! zN^(oz^l8FNiN2DcpXj)5h_SMjIU|L#banY zWmc&+Yg0}tq6pT5yoB6WjK*)|ApbUiR0$>!D%C+EhN$X!Umq1?e3|RW?9#lFay<-mrf;-)TF3w?)7Om};9O*^g8uTj)ulem!J6 zFe!gz_ZG_i(uD<^sadbMaQ-3>rU=hz3zHnjE{=sxmDj~O!Gea!zbPu3t|2YU9tB`@ z?KQnC&j3WW_1jOcNwEU?j`Ip|#RKb%&7a70WUgJBqUpXs;5f zN>(8?&$-KD)^}W$z5y^ilMYO%naw~X&Q1Q3qTO4L&S%Wfz-5xToS2v>8MaRP8n2+{ z>`I_T%w_M#|J{v*q$M&1_6Xmuvp2gEC5Ur_6ik5D zLyUEB{(R?Mz|E=BXb@la(_E=Dk~qN9MYOl3r5})gy4+Y%J`k#zP}bS~*h%>L$JIID zrH5#k2E8fpO;JhEtOanIrD=GDyg_W$MBMSYo%eI6=AlFJaFzv{E;euR)yko5lgWL7i+EgTR&IxD^Z^M@Z?D_qJV0)*TB zu_7fA^KC9UZrO2%M`#B=kcL=5y3|Q?L~h-Tw@bP7f7)uhicEZpJ13fMV)yZqLc^J5 z>9?j!7i}QQN|@QUWD_@^J)*|FBm8Bk12{}XedggCmk9AqkSUO~v zODnlgmzRKw%H-!lQ&m*?U3j-*mSgf=Aw)=n8^n9TkXLO%YIrStWY*Eiq|hcGIkC}B zd2ggq!Xc$?n&g{*p$qjWqf%K)5&ioCCPWPyhc4VWRK-$96EZ2G7~Vw|(H&E){O&_2 zg9D3h4a5RE)UJ@_jvwFZeY^V*mTdZW{di3v8f!|YHjmDxPeXb(J89mREbC+s+mnN- z?NbF7K^(nwV&f&n~u&aaFu)bCa0EETvt z_)8LYKY28e!KdwKleoEUBaZIh`E*@@^wgvG#_d5=`j| zl7O0xBssnx+zFC!%${!-M0xTSq|SId)wz4Tv*}qfCaS1Bo&*CBaR=mm8r)7O6-~cM z_{MEoHmJ@M6#yUC9&;hM2%QT0{3P)kG<`>fziM>b?@%07Y7zm9Ax`KGU3gi>uTi5F z$a5FfziwhbZV+ZKMTG`OTiVlAb%^zoF{=IlAeW$Tx{E{n$TKiX&dLH-Mlk%oSXB*E zhE378C@ssQN98u9Byq;&LEgx|xb+e`SPTZSu!OCD!^X!0i=PN0Y-wPsM(zIou;fDR zfDl}}cDm9~vRgnDrQY!y=>Bq$l0-wF|F4YFv9#ykX@xQPpI5@Pm1bts&y!Z|XE?1t z0cGS|rg_YVRXVH)h^W|h5mJ}h@DXqluK`$X%c%MyHPl}b82_y{SxMNo9bny#($~~iE=@@BE(&qQ##|cBMp~sXlP8NrJwa1cR1yt zH^67=lyl;+0W(6u*5ayMbn|S^QzzrgIZOYJZvVWNAcv`O<1ryV_TPXO>CxaZ4f_*S z3Iz>N`amE8to1l$+nl4YYhM5K!3Hjnm-!(XnkRyxzu&|I-!ZxCPQdCLST0u-ur5^i zb&uALNSJ+TZ)a!;M3w2>{0;W%RZY<~xEFknXfML-3Vy#50FV*kwKA646V!jP$7@}X zIfByXBN=6@4h%CF>M(*vI0PZ%+L5WTsPH(Uz9ofEWJ^li$4(J}vlA1tSqDbRV=kv_ za_KY;q2~Z#?S7aUW&#u-3cIN03G+0V=r)1A(l6Pg>mrQ@KLCLv1Prv97*Y`kiL7{N2$C}fdKXZxK;q>tCVXO`yEZxS^uSpO zl4ZvtsVSceyVDK3+z?~&^J!&Oe;oGKsSziX&8 zy=R%ncueY?KwYY{FYgmlR&ztb!XgJ|tv=$t;?~Q(eQTH2wI_vg^Zrv3;g_BYrO+M- z8-dY2rcK0oJX#sCY%1!Pa)KGeucOi*8aZHeTe0RkAPPf{#%!#Na%-H)wWxD6(V19g zW6mFfJJ3yq9GXmlOceF z6?$TU8gEv#U-)3ZZ1!g*7q}h>oado`a_eF&uHNYdZ`nC$uoj9!a_}u|`_Tf%PF0snWhgdqi&6gld~R7>A-?qJfGN9a#F7dC5&d*@5nmJXe5( z!V>_aMp>fQKhIjvJbd7dY9%Q0L{74i0A>+(M355DQ2wiPbO9XuBwMb2i0dGcG#+sx zdw(v_;X2e0P^$SwR(RO9WRc?C=KF`QJ3#)$QX$cb>Vvj)J?*DrcFCaylUHi99ou;Oh0%YVkW&UsU>) zFY`cixj;$7E6O=<_jU23{wX%$h)ZkEez2hoUs>PRQHuC`eRA`E34z$EPKODdD7RIP zX8@ir8-Jn?xYqg8R>iQ7NpQoZ{pz8o2xGu*o+MO}No9v@;8*|fuS)+`wSsMjxZAS6 zaV=cV60gx^rkc9ari9|0u(GLTg?`znp#b|%_0?AwZIgc&O~{ytVkKfpZDY%G7EC_fll2RT5mme*V>~VN)a?lSK1;lHyJo>7sBAP zdKVy_%@YTciN8~;_3n;g0XzGFQeR3*=;+vvMx@dMn&XrJe zV!+ZX>B|NqT62|RB`+5mqJfTdL$hoa9@XJ$!rDVbM9n^%6itd&CRag?X5#?6DgvxO zM44_~Jf^NwLMsc0x%|F&={S3WMuOg*}{YqZ+ z?_c#6UCULr?*qY^SKPHh9 z`V2bq;g_v4%W8Qf(T*<`-yR99lrx$PEazJN5<*$jUP;+uCYwcE(%ks|50a>O@g#%8 zG-=K3L@=YHiMif+%;?uyYcuee;#|6Dvj0Q9zA++way`KOMfWR7J$u)E&-wpYrr_ZSU?XBMq zw~~4p4hZ`khDJ835kA}gJV2u*<`K3ul;s!IbLtmC4#v&N#nRf5vWtx+E zvNZ%D6U7yNO<(#zGSN`GT~;mxXAC3Q%!`FFzmP5il|7s3oPd#n-Qu zcRxq3z9J?~&7W_SEGO(Z0+Jz2uAY<~%P@6aWo$(6;fatQG+d>=$k%hbe&^~aS1<(U z6S4#xnr4*rkIhAQvun}AqnNpBtt=Pf2y7-oKlfapwJ>QvLF&FzO@FgkDR#X_3Lww; zERGxmU1KL1f(M~EXmgI~iBK>J67^~IPUL&iU1|_;{VqO5Gn{Dk9CzdzS-EwVNvX&~ zTA%m)3ujzI-9jm3E4AcChWLKn>M#p#+mTnA^&4E9-wuf8T)z>JKuJwGyAhdenD!6gO;rI^@7T?+8?RMidM*v znpQJs+w^fJC(5;H_iz>e?YTG(&Te?Jvd)@vtt*=!{d1GpySGw_mvd*^byF%NiHb8v zY(!WM&O!TgPpO=6cD?_WnzX>&f0-H@rje0kGU4Fn5mmp8@)TM2;0e0}&A)PKIbWhz z%Dd|K8W3P!FeBBxWy;-kmBG0kg`}m-HdPp4av3{NxD^xnBlBRd-b%tlEnu@I*S3`W zRP*Tr>VNfE%q&7|Z8jsPKYdKp=u6lMQO|6K&k;C!z?O{e^6iVAGQ)SQX%{ZxnNUn% zdMKnnI9cjq_7&~5k@ok}@Al626z9{#k@VrfFhPg{$$bv6mTxCP(bN7Frq6kotwINZ zxwgUrF%Lr=gOuDjXgs#d$J?B+!a}Df{p6m{3gs)H+%IEWb+b{X>{d2c`hE`1MZS;g z25V^M$Sp$zbcuMTN@GHE(WihlD!LL2Sua%lj}0aiP+F(n>}PY>FjVqT$>Ja``%kzOIp=+Sopmnv z3%&XdmcEL|uzWZ6W)xzRadS;lWNW#*3j)kO7($+^zJT325^}&&VAu#m;_d6WvKFID z$-3a!2@&(mp*FZIb5VhsT_YEK+??a9(5TJ82~Mx~lXy64Nj?<{9 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/40.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/40.png new file mode 100644 index 0000000000000000000000000000000000000000..a094b82304c961e077a54013ff5514adba86b373 GIT binary patch literal 11767 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 XZ}pe4{u%@fVZQ-OD}kydO@jXqkWt-n literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/45.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/45.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e07c6957512b56595b696ecef546ba1c10155c GIT binary patch literal 11428 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/50.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/50.png new file mode 100644 index 0000000000000000000000000000000000000000..28401cc0a200a82e001b3af0a337d78b416981c0 GIT binary patch literal 12086 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 Qm|=&Ikx&$`5;F?=AE;}Rpa1{> literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/55.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/55.png new file mode 100644 index 0000000000000000000000000000000000000000..ed446b3b6060506eea21f1cc4493d4c9b97aa4e5 GIT binary patch literal 11772 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

Qzin%8H+$6gwfGj!8&$~GP`h{e} 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 wo+4PY#%gRfY|Ojgz0dt)cAlAMX7-)4 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%0D(lk>q*u=CqZRo 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/85.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/85.png new file mode 100644 index 0000000000000000000000000000000000000000..04957300bc60b7a9dac1b94d3a4a88a84bfe0bfb GIT binary patch literal 11870 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 literal 0 HcmV?d00001 diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/0.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/0.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/0.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/0.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/10.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/10.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/10.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/10.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/15.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/15.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/15.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/15.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/20.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/20.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/20.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/20.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/25.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/25.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/25.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/25.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/30.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/30.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/30.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/30.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/35.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/35.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/35.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/35.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/40.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/40.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/40.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/40.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/45.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/45.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/45.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/45.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/5.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/5.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/5.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/5.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/50.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/50.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/50.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/50.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/55.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/55.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/55.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/55.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/60.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/60.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/60.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/60.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/65.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/65.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/65.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/65.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/70.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/70.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/70.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/70.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/75.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/75.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/75.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/75.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/80.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/80.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/80.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/80.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/85.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/85.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/85.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/85.png diff --git a/TLM/TLM/Resources/SpeedLimits/Mph/90.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/90.png similarity index 100% rename from TLM/TLM/Resources/SpeedLimits/Mph/90.png rename to TLM/TLM/Resources/SpeedLimits/Mph_US/90.png diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 1a80efa18..29139fd51 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -237,5 +237,6 @@ Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour Road_signs_theme_mph Theme for MPH road signs -theme_Square_US Square US white signs -theme_Round_UK Round UK signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index a4fdf4d8a..ffb7132ef 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -232,6 +232,10 @@ Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on Ignore_disabled_mods Ignore disabled mods Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde -Display_speed_limits_mph Display speed limits as MPH instead of km/h -Miles_per_hour Miles per Hour -Kilometers_per_hour Kilometers per Hour +Display_speed_limits_mph Geschwindigkeitsbegrenzungen anzeigen als MPH statt km/h +Miles_per_hour Meilen pro Stunde +Kilometers_per_hour Kilometer pro Stunde +Road_signs_theme_mph Thema für MPH Verkehrszeichen +theme_Square_US US-Verkehrszeichen +theme_Round_UK Britische Verkehrszeichen +theme_Round_German Deutsche Verkehrszeichen diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 2bf197b63..737e137a7 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpe Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index 80a4c16b3..edc79a174 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict Afficher un message d'erreur si Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index 259816174..54a03fdbe 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpe Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index 8e816bc06..e7322ffae 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出さ Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index 78d4fe3ac..9b25fab8b 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict 모드와 비 호환되는 모 Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 3b1b4e751..be1900475 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpe Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 789fbdc1b..67f132bcf 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczeki Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index a38fabafb..af559bbef 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpe Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index f24ae01da..c246ec102 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -232,6 +232,10 @@ Scan_for_known_incompatible_mods_on_startup Сканирование извес Ignore_disabled_mods Игнорировать отключённые моды Traffic_Manager_detected_incompatible_mods Traffic Manager обнаружил несовместимые моды Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов -Display_speed_limits_mph Display speed limits as MPH instead of km/h -Miles_per_hour Miles per Hour -Kilometers_per_hour Kilometers per Hour +Display_speed_limits_mph Показывать скорости в милях в час вместо км/ч +Miles_per_hour Мили в час +Kilometers_per_hour Километры в час +Road_signs_theme_mph Стиль знаков, когда показываются мили в час +theme_Square_US Американские знаки +theme_Round_UK Британские знаки +theme_Round_German Немецкие знаки diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index aa3e530fb..64f3e2f21 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpe Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index 28b82fc41..27375c8cd 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -235,3 +235,7 @@ Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 Display_speed_limits_mph Display speed limits as MPH instead of km/h Miles_per_hour Miles per Hour Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index a5d2de9a7..97095a205 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -501,8 +501,9 @@ private static void setupSpeedLimitsPanel(UIHelper panelHelper, UIHelperBase gen var mphThemeOptions = new[] { Translation.GetString("theme_Square_US"), - Translation.GetString("theme_Round_UK") - }; + Translation.GetString("theme_Round_UK"), + Translation.GetString("theme_Round_German"), + }; roadSignMphStyleInt = (int)GlobalConfig.Instance.Main.MphRoadSignStyle; roadSignsMphThemeDropdown = generalGroup.AddDropdown( Translation.GetString("Road_signs_theme_mph") + ":", @@ -735,8 +736,19 @@ private static void onRoadSignsMphThemeChanged(int newRoadSignStyle) { if (!checkGameLoaded()) { return; } - var newStyle = newRoadSignStyle == 1 ? MphSignStyle.RoundUK : MphSignStyle.SquareUS; - Log._Debug($"Road Sign theme changed to {newStyle}"); + + // The UI order is: US, UK, German + var newStyle = MphSignStyle.RoundGerman; + switch (newRoadSignStyle) { + case 1: + newStyle = MphSignStyle.RoundUK; + break; + case 0: + newStyle = MphSignStyle.SquareUS; + break; + } + + Log._Debug($"Road Sign theme changed to {newStyle}"); GlobalConfig.Instance.Main.MphRoadSignStyle = newStyle; } diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 8e20787ee..404934bdd 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -473,25 +473,46 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index 7f19effa9..5c2c61509 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -14,6 +14,7 @@ public enum SpeedUnit { public enum MphSignStyle { SquareUS = 0, RoundUK = 1, + RoundGerman = 2, } ///

@@ -160,5 +161,16 @@ public static float GetNext(float speed) { } } + /// + /// For US signs and MPH enabled, scale textures vertically by 1.25f. + /// Other signs are round. + /// + /// Multiplier for horizontal sign size + public static float GetVerticalTextureScale() { + return (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph && + GlobalConfig.Instance.Main.MphRoadSignStyle == MphSignStyle.SquareUS) + ? 1.25f + : 1.0f; + } } } \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 5aad14c52..e61616ff3 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -445,7 +445,7 @@ private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { if (GUILayout.Button( TextureResources.GetSpeedLimitTexture(speedLimit), GUILayout.Width(signSize), - showMph ? GUILayout.Height(signSize * 1.25f) : GUILayout.Height(signSize))) { + GUILayout.Height(signSize * SpeedLimit.GetVerticalTextureScale()))) { currentPaletteSpeedLimit = speedLimit; } GUILayout.FlexibleSpace(); @@ -477,6 +477,10 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo if (!viewOnly) { showPerLane = showLimitsPerLane ^ (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)); } + + // US signs are rectangular, all other are round + var speedLimitSignVerticalScale = SpeedLimit.GetVerticalTextureScale(); + if (showPerLane) { // show individual speed limit handle per lane int numDirections; @@ -526,8 +530,8 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo var laneSpeedLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId); var hoveredHandle = MainTool.DrawGenericOverlayGridTexture( TextureResources.GetSpeedLimitTexture(laneSpeedLimit), - camPos, zero, f, f, xu, yu, x, 0, speedLimitSignSize, - GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? speedLimitSignSize * 1.25f : speedLimitSignSize, + camPos, zero, f, f, xu, yu, x, 0, + speedLimitSignSize, speedLimitSignSize * speedLimitSignVerticalScale, !viewOnly); if (!viewOnly @@ -590,7 +594,7 @@ private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, boo var boundingBox = new Rect(screenPos.x - (size / 2), screenPos.y - (size / 2), size, - GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? size * 1.25f : size); + size * speedLimitSignVerticalScale); var hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index 0b94897b8..c5108b9a6 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -48,7 +48,8 @@ public class TextureResources { public static readonly Texture2D ClockPauseTexture2D; public static readonly Texture2D ClockTestTexture2D; public static readonly IDictionary SpeedLimitTexturesKmph; - public static readonly IDictionary SpeedLimitTexturesMph; + public static readonly IDictionary SpeedLimitTexturesMphUS; + public static readonly IDictionary SpeedLimitTexturesMphUK; public static readonly IDictionary> VehicleRestrictionTextures; public static readonly IDictionary VehicleInfoSignTextures; public static readonly IDictionary ParkingRestrictionTextures; @@ -137,7 +138,8 @@ static TextureResources() { // TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor SpeedLimitTexturesKmph = new TinyDictionary(); - SpeedLimitTexturesMph = new TinyDictionary(); + SpeedLimitTexturesMphUS = new TinyDictionary(); + SpeedLimitTexturesMphUK = new TinyDictionary(); // Load shared speed limit signs for Kmph and Mph // Assumes that signs from 0 to 140 with step 5 exist, 0 denotes no limit sign @@ -147,8 +149,12 @@ static TextureResources() { } // Signs from 0 to 90 for MPH for (var speedLimit = 0; speedLimit <= 90; speedLimit += 5) { - var resource = LoadDllResource($"SpeedLimits.Mph.{speedLimit}.png", 200, 250); - SpeedLimitTexturesMph.Add(speedLimit, resource ?? SpeedLimitTexturesMph[5]); + // Load US textures, they are rectangular + var resourceUs = LoadDllResource($"SpeedLimits.Mph_US.{speedLimit}.png", 200, 250); + SpeedLimitTexturesMphUS.Add(speedLimit, resourceUs ?? SpeedLimitTexturesMphUS[5]); + // Load UK textures, they are square + var resourceUk = LoadDllResource($"SpeedLimits.Mph_UK.{speedLimit}.png", 200, 200); + SpeedLimitTexturesMphUK.Add(speedLimit, resourceUk ?? SpeedLimitTexturesMphUK[5]); } VehicleRestrictionTextures = new TinyDictionary>(); @@ -231,10 +237,30 @@ public static Texture2D GetSpeedLimitTexture(float speedLimit) { return GetSpeedLimitTexture(speedLimit, m.MphRoadSignStyle, unit); } + /// + /// Given the float speed, style and MPH option return a texture to render. + /// + /// float speed + /// Signs theme + /// Mph or km/h + /// public static Texture2D GetSpeedLimitTexture(float speedLimit, MphSignStyle mphStyle, SpeedUnit unit) { // Select the source for the textures based on unit and the theme var mph = unit == SpeedUnit.Mph; - var textures = mph ? SpeedLimitTexturesMph : SpeedLimitTexturesKmph; + var textures = SpeedLimitTexturesKmph; + if (mph) { + switch (mphStyle) { + case MphSignStyle.SquareUS: + textures = SpeedLimitTexturesMphUS; + break; + case MphSignStyle.RoundUK: + textures = SpeedLimitTexturesMphUK; + break; + case MphSignStyle.RoundGerman: + // Do nothing, this is the default above + break; + } + } // Trim the range if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { From ef63527b0619dd3416e6f121e3435ee0f290de37 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Fri, 21 Jun 2019 03:09:56 +0200 Subject: [PATCH 074/142] Fixed km/h in debug overlay for vehicles --- TLM/TLM/UI/TrafficManagerTool.cs | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 46cc771e8..b3a3666b6 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -948,27 +948,26 @@ private void _guiVehicles() { ExtCitizenInstance driverInst = ExtCitizenInstanceManager.Instance.ExtInstances[CustomPassengerCarAI.GetDriverInstanceId((ushort)i, ref Singleton.instance.m_vehicles.m_buffer[i])]; bool startNode = vState.currentStartNode; ushort segmentId = vState.currentSegmentId; - float vehSpeed = SpeedLimit.ToKmphRounded(vehicle.GetLastFrameVelocity().magnitude); + // Some magical constant converting magnitudes into km/h + float vehSpeed = vehicle.GetLastFrameVelocity().magnitude * 5f; #if DEBUG if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { continue; } #endif - - string labelStr = "V #" + i + " is a " + (vState.recklessDriver ? "reckless " : string.Empty) - + vState.flags + " " + vState.vehicleType + " @ ~" + vehSpeed + " km/h [^2=" - + vState.SqrVelocity + "] (len: " + vState.totalLength + ", " - + vState.JunctionTransitState + " @ " + vState.currentSegmentId - + " (" + vState.currentStartNode + "), l. " + vState.currentLaneIndex - + " -> " + vState.nextSegmentId + ", l. " + vState.nextLaneIndex + "), w: " - + vState.waitTime + "\n" + "di: " + driverInst.instanceId - + " dc: " + driverInst.GetCitizenId() + " m: " + driverInst.pathMode - + " f: " + driverInst.failedParkingAttempts + " l: " + driverInst.parkingSpaceLocation - + " lid: " + driverInst.parkingSpaceLocationId + " ltsu: " + vState.lastTransitStateUpdate - + " lpu: " + vState.lastPositionUpdate + " als: " + vState.lastAltLaneSelSegmentId - + " srnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetStaticVehicleRand((ushort)i) - + " trnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetTimedVehicleRand((ushort)i); + var labelStr = + $"V #{i} is a {(vState.recklessDriver ? "reckless " : string.Empty)}{vState.flags} " + + $"{vState.vehicleType} @ ~{vehSpeed:0.0} km/h (len: {vState.totalLength:0.0}, " + + $"{vState.JunctionTransitState} @ {vState.currentSegmentId} " + + $"({vState.currentStartNode}), l. {vState.currentLaneIndex} -> {vState.nextSegmentId}, " + + $"l. {vState.nextLaneIndex}), w: {vState.waitTime}\n" + + $"di: {driverInst.instanceId} dc: {driverInst.GetCitizenId()} m: {driverInst.pathMode} " + + $"f: {driverInst.failedParkingAttempts} l: {driverInst.parkingSpaceLocation} " + + $"lid: {driverInst.parkingSpaceLocationId} ltsu: {vState.lastTransitStateUpdate} " + + $"lpu: {vState.lastPositionUpdate} als: {vState.lastAltLaneSelSegmentId} " + + $"srnd: {Constants.ManagerFactory.VehicleBehaviorManager.GetStaticVehicleRand((ushort) i)} " + + $"trnd: {Constants.ManagerFactory.VehicleBehaviorManager.GetTimedVehicleRand((ushort) i)}"; Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); From ee50d3c50ed63a17b8e0881d76c25c12056975b0 Mon Sep 17 00:00:00 2001 From: Dmytro Lytovchenko Date: Fri, 21 Jun 2019 16:45:19 +0200 Subject: [PATCH 075/142] Update TLM/TLM/UI/TrafficManagerTool.cs Co-Authored-By: Krzysztof --- TLM/TLM/UI/TrafficManagerTool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index b3a3666b6..1d54baa89 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -950,7 +950,7 @@ private void _guiVehicles() { ushort segmentId = vState.currentSegmentId; // Some magical constant converting magnitudes into km/h - float vehSpeed = vehicle.GetLastFrameVelocity().magnitude * 5f; + float vehSpeed = SpeedLimit.ToKmphPrecise(vehicle.GetLastFrameVelocity().magnitude / 8f); #if DEBUG if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { continue; From c181f0c32dee390c9f88cbda17e58d9d66e92055 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Fri, 21 Jun 2019 20:26:39 +0200 Subject: [PATCH 076/142] Small rounding bug with GetLockFreeGameSpeedLimit; Save/load should work correct now --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 19 +++++++++++-------- TLM/TLM/State/Configuration.cs | 5 +++-- TLM/TLM/Traffic/Data/SpeedLimit.cs | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index d20df6827..bf1a52de8 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -230,7 +230,7 @@ public float GetLockFreeGameSpeedLimit(ushort segmentId, byte laneIndex, uint la float speedLimit = 0; float?[] fastArray = Flags.laneSpeedLimitArray[segmentId]; if (fastArray != null && fastArray.Length > laneIndex && fastArray[laneIndex] != null) { - speedLimit = ToGameSpeedLimit((ushort)fastArray[laneIndex]); + speedLimit = ToGameSpeedLimit((float)fastArray[laneIndex]); } else { speedLimit = laneInfo.m_speedLimit; } @@ -728,15 +728,17 @@ public bool LoadData(List data) { Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: " + $"Custom speed limit of segment {segmentId} info ({info}, name={info?.name}, " + $"lanes={info?.m_lanes} is {customSpeedLimit}"); + if (SpeedLimit.IsValidRange(customSpeedLimit)) { // lane speed limit differs from default speed limit Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: " + - $"lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); - Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, laneSpeedLimit.speedLimit); + $"lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit} km/h"); + var kmph = laneSpeedLimit.speedLimit / SpeedLimit.SPEED_TO_KMPH; // convert to game units + Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, kmph); } else { Log._Debug($"SpeedLimitManager.LoadData: " + $"Skipping lane speed limit of lane {laneSpeedLimit.laneId} " + - $"({laneSpeedLimit.speedLimit})"); + $"({laneSpeedLimit.speedLimit} km/h)"); } } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save @@ -748,14 +750,15 @@ public bool LoadData(List data) { } List ICustomDataManager>.SaveData(ref bool success) { - List ret = new List(); + var ret = new List(); foreach (var e in Flags.getAllLaneSpeedLimits()) { try { - Configuration.LaneSpeedLimit laneSpeedLimit = new Configuration.LaneSpeedLimit(e.Key, e.Value); - Log._Debug($"Saving speed limit of lane {laneSpeedLimit.laneId}: {laneSpeedLimit.speedLimit}"); + var laneSpeedLimit = new Configuration.LaneSpeedLimit(e.Key, e.Value); + Log._Debug($"Saving speed limit of lane {laneSpeedLimit.laneId}: " + + $"{laneSpeedLimit.speedLimit*SpeedLimit.SPEED_TO_KMPH} km/h"); ret.Add(laneSpeedLimit); } catch (Exception ex) { - Log.Error($"Exception occurred while saving lane speed limit @ {e.Key}: {ex.ToString()}"); + Log.Error($"Exception occurred while saving lane speed limit @ {e.Key}: {ex}"); success = false; } } diff --git a/TLM/TLM/State/Configuration.cs b/TLM/TLM/State/Configuration.cs index a0980b3ee..fe32cd6b5 100644 --- a/TLM/TLM/State/Configuration.cs +++ b/TLM/TLM/State/Configuration.cs @@ -4,6 +4,7 @@ using System.Runtime.Serialization; using System.Xml.Serialization; using TrafficManager.State; +using TrafficManager.Traffic.Data; // TODO this class should be moved to TrafficManager.State, but the deserialization fails if we just do that now. Anyway, we should get rid of these crazy lists of arrays. So let's move the class when we decide rework the load/save system. namespace TrafficManager { @@ -12,11 +13,11 @@ public class Configuration { [Serializable] public class LaneSpeedLimit { public uint laneId; - public float speedLimit; + public ushort speedLimit; public LaneSpeedLimit(uint laneId, float speedLimit) { this.laneId = laneId; - this.speedLimit = speedLimit; + this.speedLimit = (ushort)(speedLimit * SpeedLimit.SPEED_TO_KMPH); } } diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index 5c2c61509..f4a173c3d 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -22,7 +22,7 @@ public enum MphSignStyle { /// for when the option is set to display Mph. The engine still uses kmph. /// public struct SpeedLimit { - private const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h + public const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h private const ushort LOWER_KMPH = 10; public const ushort UPPER_KMPH = 140; private const ushort KMPH_STEP = 10; From eadc4df9270e7ef745889e68e7cdcd0e2c96db7d Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sat, 22 Jun 2019 16:46:51 +0200 Subject: [PATCH 077/142] Update PL translation --- TLM/TLM/Resources/lang_pl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 20cbb3f57..ec337cb1f 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -241,4 +241,4 @@ Keybind_use_junction_restrictions_tool Narzędzie 'Ograniczenia na skrzyżowaniu Keybind_use_speed_limits_tool Narzędzie 'Limity prędkości' Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie Keybinds Skróty klawiszowe -Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_Exit_subtool Wyłącz narzędzie i zamknij TM:PE menu From 2145ec4ac7cede7dc72dd89f928484766b70fa1a Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sat, 22 Jun 2019 16:48:07 +0200 Subject: [PATCH 078/142] Update DE translation --- TLM/TLM/Resources/lang_de.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index 6f2bdb9b8..97a1bea11 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -241,4 +241,4 @@ Keybind_use_junction_restrictions_tool Werkzeug 'Kreuzungsbeschränkungen' verwe Keybind_use_speed_limits_tool Werkzeug 'Geschwindigkeitsbeschränkungen' verwenden Keybind_lane_connector_stay_in_lane Fahrspurverbinder: Bleib auf der Fahrspur Keybinds Tastenkombinationen -Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_Exit_subtool Werkzeug und TM:PE Menü schließen From c24531928bd2a47b7c2ba91c4e5e3084e6b993d6 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sat, 22 Jun 2019 17:42:57 +0200 Subject: [PATCH 079/142] Fix kmph in 'set lane limit' debug log --- TLM/TLM/Manager/Impl/SpeedLimitManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index bf1a52de8..a83911a1b 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -528,7 +528,8 @@ public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, float sp goto nextIter; } #if DEBUG - Log._Debug($"SpeedLimitManager: Setting speed limit of lane {curLaneId} to {speedLimit}"); + Log._Debug($"SpeedLimitManager: Setting speed limit of lane {curLaneId} " + + $"to {speedLimit * SpeedLimit.SPEED_TO_KMPH}"); #endif Flags.setLaneSpeedLimit(curLaneId, speedLimit); From d532bcee2a7fd19652a58ab14fb1157415ec0275 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sat, 22 Jun 2019 19:43:37 +0200 Subject: [PATCH 080/142] mph -> MPH; Fixed Settings being out of sync with MPH checkbox in the speeds palette --- TLM/TLM/State/Options.cs | 10 +++++++--- TLM/TLM/Traffic/Data/SpeedLimit.cs | 2 +- TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 7 ++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index 97095a205..f97537c80 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -497,8 +497,6 @@ private static void setupSpeedLimitsPanel(UIHelper panelHelper, UIHelperBase gen Translation.GetString("Display_speed_limits_mph"), GlobalConfig.Instance.Main.DisplaySpeedLimitsMph, onDisplayMphChanged) as UICheckBox; - - var mphThemeOptions = new[] { Translation.GetString("theme_Square_US"), Translation.GetString("theme_Round_UK"), @@ -732,7 +730,13 @@ private static void onDisplayMphChanged(bool newValue) { GlobalConfig.WriteConfig(); } - private static void onRoadSignsMphThemeChanged(int newRoadSignStyle) { + public static void setDisplayInMPH(bool value) { + if (displayMphToggle != null) { + displayMphToggle.isChecked = value; + } + } + + private static void onRoadSignsMphThemeChanged(int newRoadSignStyle) { if (!checkGameLoaded()) { return; } diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index f4a173c3d..7f72d4c6a 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -71,7 +71,7 @@ public static string ToMphPreciseString(float speed) { return Translation.GetString("Speed_limit_unlimited"); } - return ToMphPrecise(speed) + " mph"; + return ToMphPrecise(speed) + " MPH"; } public static string ToKmphPreciseString(float speed) { diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index e61616ff3..71da933c1 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -73,7 +73,9 @@ public override void OnToolGUI(Event e) { var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? Translation.GetString("Miles_per_hour") : Translation.GetString("Kilometers_per_hour")) + ")"; - paletteWindowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph ? 10 * (GuiSpeedSignSize + 5) : 8 * (GuiSpeedSignSize + 5); + paletteWindowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? 10 * (GuiSpeedSignSize + 5) + : 8 * (GuiSpeedSignSize + 5); paletteWindowRect = GUILayout.Window(254, paletteWindowRect, _guiSpeedLimitsWindow, Translation.GetString("Speed_limits") + unitTitle, WindowStyle); @@ -422,8 +424,7 @@ private void _guiSpeedLimitsWindow(int num) { var displayMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; displayMph = GUILayout.Toggle(displayMph, Translation.GetString("Display_speed_limits_mph")); if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph != displayMph) { - GlobalConfig.Instance.Main.DisplaySpeedLimitsMph = displayMph; - GlobalConfig.WriteConfig(); + Options.setDisplayInMPH(displayMph); } GUILayout.FlexibleSpace(); From 2bae412d52ade458b34e893eeb461bfd8d14f9ba Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sat, 22 Jun 2019 23:24:28 +0200 Subject: [PATCH 081/142] Conflict detection for keyboard shortcuts in TM:PE and in the Game --- TLM/TLM/Resources/lang.txt | 1 + TLM/TLM/Resources/lang_de.txt | 1 + TLM/TLM/Resources/lang_es.txt | 1 + TLM/TLM/Resources/lang_fr.txt | 1 + TLM/TLM/Resources/lang_it.txt | 1 + TLM/TLM/Resources/lang_ja.txt | 1 + TLM/TLM/Resources/lang_ko.txt | 1 + TLM/TLM/Resources/lang_nl.txt | 1 + TLM/TLM/Resources/lang_pl.txt | 1 + TLM/TLM/Resources/lang_pt.txt | 1 + TLM/TLM/Resources/lang_ru.txt | 1 + TLM/TLM/Resources/lang_zh-tw.txt | 1 + TLM/TLM/Resources/lang_zh.txt | 1 + TLM/TLM/State/KeyMapping.cs | 129 +++++++++++++++++++++++++++---- 14 files changed, 126 insertions(+), 16 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index dc4c13856..265d3995f 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index 97a1bea11..d5859d6cf 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Werkzeug 'Geschwindigkeitsbeschränkungen' verwend Keybind_lane_connector_stay_in_lane Fahrspurverbinder: Bleib auf der Fahrspur Keybinds Tastenkombinationen Keybind_Exit_subtool Werkzeug und TM:PE Menü schließen +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 7fd2fd6e0..188b0f719 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index beaa8c2f1..de95a86ac 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index fb086db2e..99af1dae7 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index 6161f0a17..140651e18 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index ee6e3a47b..a88241955 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 9a774b30c..0a0d05af4 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index ec337cb1f..0b5cff0b5 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Narzędzie 'Limity prędkości' Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie Keybinds Skróty klawiszowe Keybind_Exit_subtool Wyłącz narzędzie i zamknij TM:PE menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index 3f1a1e602..7ba5fef1c 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 836a915a1..9a396f3fd 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Инструмент 'Ограничения ск Keybind_lane_connector_stay_in_lane Линии движения: Оставаться в своей полосе Keybinds Горячие клавиши Keybind_Exit_subtool Закрыть инструмент и меню TM:PE +Keybind_conflict Выбранная комбинация клавиш уже используется в другом месте. Пожалуйста, выберите другую комбинацию. diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index ec3a08bd5..ee5cd20f7 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index c025f53ca..98f09e232 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -242,3 +242,4 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict Die ausgewählte Taste steht in Konflikt mit einer anderen Tastenkombination. Bitte wähle etwas anderes. diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs index 28b112087..3f2e68230 100644 --- a/TLM/TLM/State/KeyMapping.cs +++ b/TLM/TLM/State/KeyMapping.cs @@ -2,7 +2,11 @@ // Thanks to https://github.com/Quboid/CS-MoveIt using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using ColossalFramework; using ColossalFramework.Globalization; using ColossalFramework.UI; @@ -248,8 +252,16 @@ protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { if (p.keycode == KeyCode.Backspace) { inputKey = SavedInputKey.Empty; } + var maybeConflict = FindConflict(inputKey); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n" + maybeConflict, + false); + } else { + editingBinding_.value = inputKey; + } - editingBinding_.value = inputKey; var uITextComponent = p.source as UITextComponent; uITextComponent.text = editingBinding_.ToLocalizedString("KEYNAME"); editingBinding_ = null; @@ -278,8 +290,16 @@ protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), IsControlDown(), IsShiftDown(), IsAltDown()); + var maybeConflict = FindConflict(inputKey); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n" + maybeConflict, + false); + } else { + editingBinding_.value = inputKey; + } - editingBinding_.value = inputKey; var uIButton2 = p.source as UIButton; uIButton2.text = editingBinding_.ToLocalizedString("KEYNAME"); uIButton2.buttonsMask = UIMouseButton.Left; @@ -304,30 +324,107 @@ protected void RefreshBindableInputs() { } } } + + protected void RefreshKeyMapping() { + foreach (var current in component.GetComponentsInChildren()) { + var uITextComponent = current.Find("Binding"); + var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; + if (editingBinding_ != savedInputKey) { + uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + } + } + } - protected InputKey GetDefaultEntry(string entryName) { - var field = - typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); - if (field == null) { - return 0; + /// + /// For an inputkey, try find where possibly it is already used. + /// This covers game Settings class, and self (OptionsKeymapping class). + /// + /// Key to search for the conflicts + /// + private string FindConflict(InputKey sample) { + if (sample == SavedInputKey.Empty + || sample == SavedInputKey.Encode(KeyCode.None, false, false, false)) { + // empty key never conflicts + return string.Empty; } - var value = field.GetValue(null); - if (value is InputKey) { - return (InputKey) value; + var inGameSettings = FindConflictInGameSettings(sample); + if (!string.IsNullOrEmpty(inGameSettings)) { + return inGameSettings; } + // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. + var saveEditingBinding = editingBinding_; + editingBinding_ = null; + + // Check in TMPE settings + var tmpeSettingsType = typeof(OptionsKeymapping); + var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); + + var inTmpe = FindConflictInTmpe(sample, tmpeFields); + editingBinding_ = saveEditingBinding; + return inTmpe; + } + + private static string FindConflictInGameSettings(InputKey sample) { + var fieldList = typeof(Settings).GetFields(BindingFlags.Static | BindingFlags.Public); + foreach (var field in fieldList) { + var customAttributes = field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; + if (customAttributes != null && customAttributes.Length > 0) { + var category = customAttributes[0].category; + if (category != string.Empty && category != "Game") { + // Ignore other categories: MapEditor, Decoration, ThemeEditor, ScenarioEditor + continue; + } + + var str = field.GetValue(null) as string; + + var savedInputKey = new SavedInputKey(str, + Settings.gameSettingsFile, + GetDefaultEntryInGameSettings(str), + true); + if (savedInputKey.value == sample) { + return (category == string.Empty ? string.Empty : (category + " -- ")) + + CamelCaseSplit(field.Name); + } + } + } + + return string.Empty; + } + + private static InputKey GetDefaultEntryInGameSettings(string entryName) { + var field = typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); + if (field == null) { + return 0; + } + var obj = field.GetValue(null); + if (obj is InputKey) { + return (InputKey)obj; + } return 0; } - protected void RefreshKeyMapping() { - foreach (var current in component.GetComponentsInChildren()) { - var uITextComponent = current.Find("Binding"); - var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; - if (editingBinding_ != savedInputKey) { - uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + private static string FindConflictInTmpe(InputKey sample, FieldInfo[] fields) { + foreach (var field in fields) { + // This will match inputkeys of TMPE key settings + if (field.FieldType == typeof(SavedInputKey)) { + var key = (SavedInputKey)field.GetValue(null); + if (key.value == sample) { + return "TM:PE -- " + CamelCaseSplit(field.Name); + } } } + + return string.Empty; + } + + private static string CamelCaseSplit(string s) { + var words = Regex.Matches(s, @"([A-Z][a-z]+)") + .Cast() + .Select(m => m.Value); + + return string.Join(" ", words.ToArray()); } } } \ No newline at end of file From 370222b57d885c9a158527a6eddcb34103452bcb Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 23 Jun 2019 00:36:20 +0200 Subject: [PATCH 082/142] Conflict detection improved. TMPE keys now have categories to distinguish between their usage --- TLM/TLM/Resources/lang.txt | 2 + TLM/TLM/Resources/lang_de.txt | 4 +- TLM/TLM/Resources/lang_es.txt | 2 + TLM/TLM/Resources/lang_fr.txt | 2 + TLM/TLM/Resources/lang_it.txt | 2 + TLM/TLM/Resources/lang_ja.txt | 2 + TLM/TLM/Resources/lang_ko.txt | 2 + TLM/TLM/Resources/lang_nl.txt | 2 + TLM/TLM/Resources/lang_pl.txt | 2 + TLM/TLM/Resources/lang_pt.txt | 2 + TLM/TLM/Resources/lang_ru.txt | 14 +-- TLM/TLM/Resources/lang_template.txt | 15 ++- TLM/TLM/Resources/lang_zh-tw.txt | 2 + TLM/TLM/Resources/lang_zh.txt | 4 +- TLM/TLM/State/KeyMapping.cs | 112 ++++++++++++++++++----- TLM/TLM/UI/SubTools/LaneConnectorTool.cs | 4 +- 16 files changed, 138 insertions(+), 35 deletions(-) diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 265d3995f..24327090c 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index d5859d6cf..ec2972ffb 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -242,4 +242,6 @@ Keybind_use_speed_limits_tool Werkzeug 'Geschwindigkeitsbeschränkungen' verwend Keybind_lane_connector_stay_in_lane Fahrspurverbinder: Bleib auf der Fahrspur Keybinds Tastenkombinationen Keybind_Exit_subtool Werkzeug und TM:PE Menü schließen -Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_conflict Die ausgewählte Taste steht in Konflikt mit einer anderen Tastenkombination. Bitte wähle etwas anderes. +Keybind_category_Global Allgemeine Tasten +Keybind_category_LaneConnector Tasten für 'Fahrspurverbinder' Werkzeug diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 188b0f719..8890d8c9f 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index de95a86ac..a39b08b1a 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index 99af1dae7..4734074f3 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index 140651e18..938e83cbb 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index a88241955..b8e279c97 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 0a0d05af4..234b3f569 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 0b5cff0b5..774a0a55c 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie Keybinds Skróty klawiszowe Keybind_Exit_subtool Wyłącz narzędzie i zamknij TM:PE menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index 7ba5fef1c..6af421ecd 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 9a396f3fd..90d9b3300 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -233,13 +233,15 @@ Ignore_disabled_mods Игнорировать отключённые моды Traffic_Manager_detected_incompatible_mods Traffic Manager обнаружил несовместимые моды Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов Keybind_toggle_TMPE_main_menu Показать или скрыть меню TM:PE -Keybind_toggle_traffic_lights_tool Инструмент 'Установка светофора' -Keybind_use_lane_arrow_tool Инструмент 'Стрелки движения' -Keybind_use_lane_connections_tool Инструмент 'Линии движения' -Keybind_use_priority_signs_tool Инструмент 'Знаки приоритета' -Keybind_use_junction_restrictions_tool Инструмент 'Ограничения на перекрёстках' -Keybind_use_speed_limits_tool Инструмент 'Ограничения скорости' +Keybind_toggle_traffic_lights_tool Инструмент "Установка светофора" +Keybind_use_lane_arrow_tool Инструмент "Стрелки движения" +Keybind_use_lane_connections_tool Инструмент "Линии движения" +Keybind_use_priority_signs_tool Инструмент "Знаки приоритета" +Keybind_use_junction_restrictions_tool Инструмент "Ограничения на перекрёстках" +Keybind_use_speed_limits_tool Инструмент "Ограничения скорости" Keybind_lane_connector_stay_in_lane Линии движения: Оставаться в своей полосе Keybinds Горячие клавиши Keybind_Exit_subtool Закрыть инструмент и меню TM:PE Keybind_conflict Выбранная комбинация клавиш уже используется в другом месте. Пожалуйста, выберите другую комбинацию. +Keybind_category_Global Общие комбинации клавиш +Keybind_category_LaneConnector Клавиши для инструмента "Линии Движения" diff --git a/TLM/TLM/Resources/lang_template.txt b/TLM/TLM/Resources/lang_template.txt index 3e1e48396..255d5ca5a 100644 --- a/TLM/TLM/Resources/lang_template.txt +++ b/TLM/TLM/Resources/lang_template.txt @@ -231,4 +231,17 @@ Also_apply_to_left/right_turns_between_one-way_streets Scan_for_known_incompatible_mods_on_startup Ignore_disabled_mods Traffic_Manager_detected_incompatible_mods -Notify_me_if_there_is_an_unexpected_mod_conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict +Keybind_toggle_TMPE_main_menu +Keybind_toggle_traffic_lights_tool +Keybind_use_lane_arrow_tool +Keybind_use_lane_connections_tool +Keybind_use_priority_signs_tool +Keybind_use_junction_restrictions_tool +Keybind_use_speed_limits_tool +Keybind_lane_connector_stay_in_lane +Keybinds +Keybind_Exit_subtool +Keybind_conflict +Keybind_category_Global +Keybind_category_LaneConnector diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index ee5cd20f7..c6d04466a 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -243,3 +243,5 @@ Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index 98f09e232..85c2708b5 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -242,4 +242,6 @@ Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu -Keybind_conflict Die ausgewählte Taste steht in Konflikt mit einer anderen Tastenkombination. Bitte wähle etwas anderes. +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs index 3f2e68230..957288c1f 100644 --- a/TLM/TLM/State/KeyMapping.cs +++ b/TLM/TLM/State/KeyMapping.cs @@ -15,30 +15,47 @@ using UnityEngine; namespace TrafficManager.State { + /// + /// This attribute is used on key bindings and tells us where this key is used, + /// to allow using the same key in multiple occasions we need to know the category. + /// + [AttributeUsage(AttributeTargets.Field)] + public class TMPERebindableKey: Attribute { + public string Category; + public TMPERebindableKey(string cat) { + Category = cat; + } + } + public class OptionsKeymappingMain : OptionsKeymapping { private void Awake() { TryCreateConfig(); + AddReadOnlyKeymapping(Translation.GetString("Keybind_Exit_subtool"), KeyToolCancel_ViewOnly); AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), - KeyToggleTMPEMainMenu); + KeyToggleTMPEMainMenu, "Global"); AddKeymapping(Translation.GetString("Keybind_toggle_traffic_lights_tool"), - KeyToggleTrafficLightTool); + KeyToggleTrafficLightTool, "Global"); AddKeymapping(Translation.GetString("Keybind_use_lane_arrow_tool"), - KeyLaneArrowTool); + KeyLaneArrowTool, "Global"); AddKeymapping(Translation.GetString("Keybind_use_lane_connections_tool"), - KeyLaneConnectionsTool); + KeyLaneConnectionsTool, "Global"); AddKeymapping(Translation.GetString("Keybind_use_priority_signs_tool"), - KeyPrioritySignsTool); + KeyPrioritySignsTool, "Global"); AddKeymapping(Translation.GetString("Keybind_use_junction_restrictions_tool"), - KeyJunctionRestrictionsTool); + KeyJunctionRestrictionsTool, "Global"); AddKeymapping(Translation.GetString("Keybind_use_speed_limits_tool"), - KeySpeedLimitsTool); + KeySpeedLimitsTool, "Global"); + // New section: Lane Connector Tool AddKeymapping(Translation.GetString("Keybind_lane_connector_stay_in_lane"), - KeyLaneConnectorStayInLane); + KeyLaneConnectorStayInLane, "LaneConnector"); + + AddKeymapping(Translation.GetString("Keybind_lane_connector_delete"), + KeyLaneConnectorDelete, "LaneConnector"); } } @@ -49,60 +66,76 @@ public class OptionsKeymapping : UICustomControl { /// /// This input key can not be changed and is not checked, instead it is display only /// + [TMPERebindableKey("Global")] public static SavedInputKey KeyToolCancel_ViewOnly = new SavedInputKey("keyExitSubtool", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Encode(KeyCode.Escape, false, false, false), false); + [TMPERebindableKey("Global")] public static SavedInputKey KeyToggleTMPEMainMenu = new SavedInputKey("keyToggleTMPEMainMenu", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Encode(KeyCode.Semicolon, false, true, false), true); + [TMPERebindableKey("Global")] public static SavedInputKey KeyToggleTrafficLightTool = new SavedInputKey("keyToggleTrafficLightTool", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Empty, true); + [TMPERebindableKey("Global")] public static SavedInputKey KeyLaneArrowTool = new SavedInputKey("keyLaneArrowTool", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Empty, true); + [TMPERebindableKey("Global")] public static SavedInputKey KeyLaneConnectionsTool = new SavedInputKey("keyLaneConnectionsTool", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Empty, true); + [TMPERebindableKey("Global")] public static SavedInputKey KeyPrioritySignsTool = new SavedInputKey("keyPrioritySignsTool", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Empty, true); + [TMPERebindableKey("Global")] public static SavedInputKey KeyJunctionRestrictionsTool = new SavedInputKey("keyJunctionRestrictionsTool", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Empty, true); + [TMPERebindableKey("Global")] public static SavedInputKey KeySpeedLimitsTool = new SavedInputKey("keySpeedLimitsTool", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Empty, true); + [TMPERebindableKey("LaneConnector")] public static SavedInputKey KeyLaneConnectorStayInLane = new SavedInputKey("keyLaneConnectorStayInLane", KEYBOARD_SHORTCUTS_FILENAME, SavedInputKey.Encode(KeyCode.S, false, true, false), true); + [TMPERebindableKey("LaneConnector")] + public static SavedInputKey KeyLaneConnectorDelete = + new SavedInputKey("keyLaneConnectorDelete", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.Delete, false, false, false), + true); + private SavedInputKey editingBinding_; private string editingBindingCategory_; @@ -126,7 +159,7 @@ protected void TryCreateConfig() { ///
/// Text to display /// A SavedInputKey from GlobalConfig.KeyboardShortcuts - protected void AddKeymapping(string label, SavedInputKey savedInputKey) { + protected void AddKeymapping(string label, SavedInputKey savedInputKey, string category) { var uiPanel = component.AttachUIComponent( UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; if (count_++ % 2 == 1) { @@ -140,11 +173,12 @@ protected void AddKeymapping(string label, SavedInputKey savedInputKey) { var uiButton = uiPanel.Find("Binding"); uiButton.eventKeyDown += OnBindingKeyDown; uiButton.eventMouseDown += OnBindingMouseDown; + uiButton.text = savedInputKey.ToLocalizedString("KEYNAME"); + uiButton.objectUserData = savedInputKey; + uiButton.stringUserData = category; // Set label text (as provided) and set button text from the SavedInputKey uiLabel.text = label; - uiButton.text = savedInputKey.ToLocalizedString("KEYNAME"); - uiButton.objectUserData = savedInputKey; } /// @@ -249,14 +283,16 @@ protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { var inputKey = (p.keycode == KeyCode.Escape) ? editingBinding_.value : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); + var category = p.source.stringUserData; + if (p.keycode == KeyCode.Backspace) { inputKey = SavedInputKey.Empty; } - var maybeConflict = FindConflict(inputKey); + var maybeConflict = FindConflict(inputKey, category); if (maybeConflict != string.Empty) { UIView.library.ShowModal("ExceptionPanel").SetMessage( "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n" + maybeConflict, + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, false); } else { editingBinding_.value = inputKey; @@ -290,11 +326,12 @@ protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), IsControlDown(), IsShiftDown(), IsAltDown()); - var maybeConflict = FindConflict(inputKey); + var category = p.source.stringUserData; + var maybeConflict = FindConflict(inputKey, category); if (maybeConflict != string.Empty) { UIView.library.ShowModal("ExceptionPanel").SetMessage( "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n" + maybeConflict, + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, false); } else { editingBinding_.value = inputKey; @@ -341,7 +378,7 @@ protected void RefreshKeyMapping() { /// /// Key to search for the conflicts /// - private string FindConflict(InputKey sample) { + private string FindConflict(InputKey sample, string sampleCategory) { if (sample == SavedInputKey.Empty || sample == SavedInputKey.Encode(KeyCode.None, false, false, false)) { // empty key never conflicts @@ -355,13 +392,13 @@ private string FindConflict(InputKey sample) { // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. var saveEditingBinding = editingBinding_; - editingBinding_ = null; + editingBinding_.value = SavedInputKey.Empty; // Check in TMPE settings var tmpeSettingsType = typeof(OptionsKeymapping); var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); - var inTmpe = FindConflictInTmpe(sample, tmpeFields); + var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); editingBinding_ = saveEditingBinding; return inTmpe; } @@ -405,14 +442,41 @@ private static InputKey GetDefaultEntryInGameSettings(string entryName) { return 0; } - private static string FindConflictInTmpe(InputKey sample, FieldInfo[] fields) { + /// + /// For given key and category check TM:PE settings for the Global category + /// and the same category if it is not Global. This will allow reusing key in other tool + /// categories without conflicting. + /// + /// The key to search for + /// The category Global or some tool name + /// Fields of the key settings class + /// Empty string if no conflicts otherwise the key name to print an error + private static string FindConflictInTmpe(InputKey sample, string sampleCategory, FieldInfo[] fields) { foreach (var field in fields) { // This will match inputkeys of TMPE key settings - if (field.FieldType == typeof(SavedInputKey)) { - var key = (SavedInputKey)field.GetValue(null); - if (key.value == sample) { - return "TM:PE -- " + CamelCaseSplit(field.Name); - } + if (field.FieldType != typeof(SavedInputKey)) { + continue; + } + + var rebindableKeyAttrs = field.GetCustomAttributes( + typeof(TMPERebindableKey), + false) as TMPERebindableKey[]; + if (rebindableKeyAttrs == null || rebindableKeyAttrs.Length <= 0) { + continue; + } + + // Check category, category=Global will check keys in all categories + // category= will check Global and its own only + var rebindableKeyCategory = rebindableKeyAttrs[0].Category; + if (sampleCategory != "Global" && sampleCategory != rebindableKeyCategory) { + continue; + } + + var key = (SavedInputKey) field.GetValue(null); + if (key.value == sample) { + return "TM:PE, " + + Translation.GetString("Keybind_category_" + rebindableKeyCategory) + + " -- " + CamelCaseSplit(field.Name); } } diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index 2badbfe6f..06e58f018 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -40,7 +40,7 @@ enum StayInLaneMode { /// Resets when the event is consumed or after a few frames. private int frameStayInLanePressed = 0; - /// Clear lane lines is Delete or Backspace + /// Clear lane lines is Delete (configurable) private int frameClearPressed = 0; class NodeLaneMarker { @@ -77,7 +77,7 @@ public override void OnToolGUI(Event e) { // not too long ago (within 20 Unity frames or 0.33 sec) } - if (Input.GetKeyDown(KeyCode.Delete) || Input.GetKeyDown(KeyCode.Backspace)) { + if (OptionsKeymapping.KeyLaneConnectorDelete.IsPressed(e)) { frameClearPressed = Time.frameCount; // this will be consumed in RenderOverlay() if the key was pressed // not too long ago (within 20 Unity frames or 0.33 sec) From c511807c60f4d8cd3ae9d50030a1e48bffe135d1 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 23 Jun 2019 23:52:53 +0200 Subject: [PATCH 083/142] Update PL translations --- TLM/TLM/Resources/lang_pl.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 67f132bcf..786cc5101 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -232,10 +232,10 @@ Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie Ignore_disabled_mods Ignoruj wyłączone mody Traffic_Manager_detected_incompatible_mods Traffic Manager wykrył niekompatybilne mody Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów -Display_speed_limits_mph Display speed limits as MPH instead of km/h -Miles_per_hour Miles per Hour -Kilometers_per_hour Kilometers per Hour -Road_signs_theme_mph Theme for MPH road signs -theme_Square_US US signs -theme_Round_UK British signs -theme_Round_German German signs +Display_speed_limits_mph Wyświetlaj limity prędkości w mph zamiast km/h +Miles_per_hour Mile na godzinę +Kilometers_per_hour Kilometry na godzinę +Road_signs_theme_mph Motyw dla znaków MPH +theme_Square_US Stany zjednoczone +theme_Round_UK Wielka Brytania +theme_Round_German Niemcy From ebf71f7fb31836c28cee4dce89e203b65f14b870 Mon Sep 17 00:00:00 2001 From: Aubergine Date: Tue, 25 Jun 2019 16:35:07 +0100 Subject: [PATCH 084/142] Undo the dreadful tab indent in .editorconfig A recent PR introduced an .editorconfig which defined `indent_style = tab` and `indent_size = 8`. Big nope to both. Changed to spaces, with indent of 4. --- TLM/.editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/.editorconfig b/TLM/.editorconfig index f9530af21..9e11c451a 100644 --- a/TLM/.editorconfig +++ b/TLM/.editorconfig @@ -1,8 +1,8 @@ root = true [*.{cs,vb}] -indent_style = tab -indent_size = 8 +indent_style = space +indent_size = 4 trim_trailing_whitespace = true [*.cs] From 515057d18e3fe74f8c62d74cec078a19594823b9 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 25 Jun 2019 20:33:03 +0100 Subject: [PATCH 085/142] Update ModsCompatibilityChecker.cs Revert to Path.GetFileName() - will fix formatting later. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 781912f6c..cf3e39d32 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -87,12 +87,13 @@ public Dictionary ScanForIncompatibleMods() else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) { Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); - string folder = mod.modPath.Split(Path.DirectorySeparatorChar).Last(); + string folder = Path.GetFileName(mod.modPath); + //string folder = mod.modPath.Split(Path.DirectorySeparatorChar).Last(); results.Add(mod, $"{modName} in /{folder}"); } #endif - } - } + } + } Log.Info($"Scan complete: {results.Count} incompatible mod(s) found"); From a240a520986d8e131367b4f8c597de5790c38d24 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 25 Jun 2019 20:34:50 +0100 Subject: [PATCH 086/142] Update ModsCompatibilityChecker.cs Use plugin.userModInsstance to get mod name. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index cf3e39d32..837e6347d 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -111,14 +111,8 @@ public Dictionary ScanForIncompatibleMods() /// The name of the specified plugin. public string GetModName(PluginInfo plugin) { - string name = plugin.name; - IUserMod[] instances = plugin.GetInstances(); - if (instances.Length > 0) - { - name = instances[0].Name; - } - return name; - } + return ((IUserMod)plugin.userModInstance).Name; + } /// /// Works out if the game is effectively running in offline mode, in which no workshop mod subscriptions will be active. From ebb5bd21870154972a52c0e797ec1815cfa6b70b Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 25 Jun 2019 20:37:29 +0100 Subject: [PATCH 087/142] Update ModsCompatibilityChecker.cs Wrap release-only `using` clause in #if/endif --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 837e6347d..ff4eb1259 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -1,5 +1,7 @@ using ColossalFramework; +#if !DEBUG using ColossalFramework.PlatformServices; // used in RELEASE builds +#endif using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; From 6fb820f2fa4623a2cfa8478cf0a7037bec565901 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Wed, 26 Jun 2019 01:55:09 +0100 Subject: [PATCH 088/142] Update IncompatibleModsPanel.cs Tidied up `using` clauses, fixed typo in a method summary, autoformat doc. --- TLM/TLM/UI/IncompatibleModsPanel.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index 0e0a80bdd..bc587dfb3 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; using ColossalFramework; -using ColossalFramework.Globalization; using ColossalFramework.IO; using ColossalFramework.PlatformServices; -using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; +using System; +using System.Collections.Generic; using UnityEngine; using static ColossalFramework.Plugins.PluginManager; @@ -25,7 +23,7 @@ public class IncompatibleModsPanel : UIPanel private UIComponent blurEffect; /// - /// List of incompatible mods from . + /// Gets or sets list of incompatible mods from . /// public Dictionary IncompatibleMods { get; set; } @@ -144,7 +142,7 @@ public void Initialize() if (blurEffect != null) { blurEffect.isVisible = true; - ValueAnimator.Animate("ModalEffect", delegate(float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); + ValueAnimator.Animate("ModalEffect", delegate (float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); } // Make sure modal dialog is in front of all other UI From 811e0bb36c0cd244871be2ad510c9bc6baae6271 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Wed, 26 Jun 2019 01:56:26 +0100 Subject: [PATCH 089/142] Update ModsCompatibilityChecker.cs Catch and log exceptions in main PerformModCheck() method, autoformat doc, removed Linq as no longer required. --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 35 ++++++++++++++---------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index ff4eb1259..20e93dfe2 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -9,9 +9,9 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; // used in RELEASE builds using System.Reflection; using TrafficManager.UI; +using UnityEngine; using static ColossalFramework.Plugins.PluginManager; namespace TrafficManager.Util @@ -38,15 +38,22 @@ public ModsCompatibilityChecker() /// public void PerformModCheck() { - Dictionary detected = ScanForIncompatibleMods(); + try + { + Dictionary detected = ScanForIncompatibleMods(); - if (detected.Count > 0 && State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) + if (detected.Count > 0 && State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) + { + IncompatibleModsPanel panel = UIView.GetAView().AddUIComponent(typeof(IncompatibleModsPanel)) as IncompatibleModsPanel; + panel.IncompatibleMods = detected; + panel.Initialize(); + UIView.PushModal(panel); + UIView.SetFocus(panel); + } + } + catch (Exception e) { - IncompatibleModsPanel panel = UIView.GetAView().AddUIComponent(typeof(IncompatibleModsPanel)) as IncompatibleModsPanel; - panel.IncompatibleMods = detected; - panel.Initialize(); - UIView.PushModal(panel); - UIView.SetFocus(panel); + Debug.LogException(e); } } @@ -89,13 +96,13 @@ public Dictionary ScanForIncompatibleMods() else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) { Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); - string folder = Path.GetFileName(mod.modPath); + string folder = Path.GetFileName(mod.modPath); //string folder = mod.modPath.Split(Path.DirectorySeparatorChar).Last(); results.Add(mod, $"{modName} in /{folder}"); } #endif - } - } + } + } Log.Info($"Scan complete: {results.Count} incompatible mod(s) found"); @@ -113,8 +120,8 @@ public Dictionary ScanForIncompatibleMods() /// The name of the specified plugin. public string GetModName(PluginInfo plugin) { - return ((IUserMod)plugin.userModInstance).Name; - } + return ((IUserMod)plugin.userModInstance).Name; + } /// /// Works out if the game is effectively running in offline mode, in which no workshop mod subscriptions will be active. @@ -184,7 +191,7 @@ private Dictionary LoadListOfIncompatibleMods() // Treat other workshop-published branches of TM:PE, as applicable, as conflicts #if LABS - results.Add(583429740u , "TM:PE STABLE"); + results.Add(583429740u, "TM:PE STABLE"); #elif DEBUG results.Add(1637663252u, "TM:PE LABS"); results.Add(583429740u, "TM:PE STABLE"); From 9d23732c5e526af30f2210583dc7a39bb01478d6 Mon Sep 17 00:00:00 2001 From: Aubergine Date: Wed, 26 Jun 2019 20:01:05 +0100 Subject: [PATCH 090/142] .gitignore /TLM/.vs/config/applicationhost.config IIS garbage --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cf1a6dc28..f1f3e2aa3 100644 --- a/.gitignore +++ b/.gitignore @@ -186,6 +186,7 @@ FakesAssemblies/ # MSVS 2017 artifacts /.vs/slnx.sqlite /TLM/.vs/TMPE/* +/TLM/.vs/config/applicationhost.config # Dependecies game dlls -/TLM/dependencies \ No newline at end of file +/TLM/dependencies From fa16ae0c678fd9a85745595ee54052e540d0d088 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Wed, 26 Jun 2019 20:06:25 +0100 Subject: [PATCH 091/142] Delete applicationhost.config --- TLM/.vs/config/applicationhost.config | 1028 ------------------------- 1 file changed, 1028 deletions(-) delete mode 100644 TLM/.vs/config/applicationhost.config diff --git a/TLM/.vs/config/applicationhost.config b/TLM/.vs/config/applicationhost.config deleted file mode 100644 index 5a8a09d3f..000000000 --- a/TLM/.vs/config/applicationhost.config +++ /dev/null @@ -1,1028 +0,0 @@ - - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From c7924bacf6fc6e0d5843bd83184e4dd1addcd8d3 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Wed, 26 Jun 2019 20:41:06 +0100 Subject: [PATCH 092/142] Ignore entire .vs/ folder As per krzychu request in dev discord --- .gitignore | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f1f3e2aa3..23d1fd77d 100644 --- a/.gitignore +++ b/.gitignore @@ -125,7 +125,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -184,9 +184,7 @@ FakesAssemblies/ /logo/ # MSVS 2017 artifacts -/.vs/slnx.sqlite -/TLM/.vs/TMPE/* -/TLM/.vs/config/applicationhost.config +.vs/ # Dependecies game dlls /TLM/dependencies From ef7122ac8979683b6b9f96f8e12a7470ff12862a Mon Sep 17 00:00:00 2001 From: Aubergine Date: Wed, 26 Jun 2019 23:49:12 +0100 Subject: [PATCH 093/142] Update README for upcoming 10.21 release Will push commits for changelog, etc., shortly. --- README.md | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 491d07f82..520d03089 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ A mod for **Cities: Skylines** that gives you more control over road and rail tr [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=583429740) • [Discord Guild](https://discord.gg/faKUnST) • [Installation](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/wiki/Installation) • [User Guide](http://www.viathinksoft.de/tmpe/wiki) • [Issue Tracker](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues) • [Report a Bug](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/wiki/Report-a-Bug) +> Users having problems with traffic despawning after updating roads or rails are advised to try the [Broken Node Detector](https://steamcommunity.com/sharedfiles/filedetails/?id=1777173984) which helps detect a game bug. Co are aware of the issue. + # Features * Timed traffic lights @@ -18,14 +20,42 @@ A mod for **Cities: Skylines** that gives you more control over road and rail tr * Vehicle restrictions * For roads and trains! * Customise speed limits + * In MPH or km/h * Toggle despawn * Clear traffic, stuck cims, etc. # Changelog -### [10.20](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.19...10.20), 21/05/2019 -- Updated for game version 1.12.0-f5 -- Updated Korean translation (thanks Twotoolus-FLY-LShst) (#294) -- Updated French translation (thanks PierreTSE) (#311) +### [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), ??/07/2019 +* Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) +* Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) +* Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) +* Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) +* Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) +* Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid!) (#362) +* Fixed: Mail trucks ignoring lane arrows (#307, #338) +* Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) +* Fixed: Random parking broken (thanks s2500111 for beta testing) (#259, #359) +* Fixed: Pedestrian crossing restriction affects road-side parking (#259, #359) +* Fixed: 'Vanilla Trees Remover' is now compatible (thanks TPB for fixing) (#331, #332) +* Fixed: Single-lane bunching on DLS higher than 50% (#263 #334) +* Fixed: Lane changes at toll booths (also notified CO of bug in vanilla) (#225, #355) +* Fixed: Minor issues regarding saving/loading junction restrictions (#358) +* Fixed: Changes of default junction restrictions not reflected in UI overlay (#358) +* Fixed: Resetting stuck cims unpauses the simulation (#358, #351) +* Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190) +* Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +* Updated: Chinese translation (thanks Emphasia) (#375, #336) +* Updated: German translation (thanks kvakvs) (#384) +* Updated: Polish translation (thanks krzychu124) (#384, #333) +* Updated: Russian translation (thanks vitalii201) (#327, #328) +* Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) +* Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) +* Meta: Pathfinder debug logging tools (switch `26`) (#370) +* Meta: Separate binaries for Stable and Labs on GitHub release pages (#360) +* Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) +* Meta: Added GitHub issue templates for bugs, features, translations. (#272) +* Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) +* Meta: Added entire `.vs/` folder to `.gitignore` (#395) See [Full Changelog](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/blob/master/CHANGELOG.md) for details of earlier releases. From e9b92181128a7f71bb8febeec29a6a35e0683023 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 27 Jun 2019 01:40:44 +0100 Subject: [PATCH 094/142] Improved formatting of header (logo, centering, etc) Also removed the features list as it's better suited to the wiki. --- README.md | 47 +++++++++++------------------------------------ 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 520d03089..bfc53d58e 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,13 @@ -# Traffic Manager: *President Edition* [![Steam](https://img.shields.io/endpoint.svg?url=https://shieldsio-steam-workshop.jross.me/583429740)](https://steamcommunity.com/sharedfiles/filedetails/?id=583429740) [![Discord](https://img.shields.io/discord/545065285862948894.svg?logo=discord&logoColor=F5F5F5)](https://discord.gg/faKUnST) [![Build status](https://ci.appveyor.com/api/projects/status/dehkvuxk8b3h66e7/branch/master?svg=true)](https://ci.appveyor.com/project/krzychu124/cities-skylines-traffic-manager-president-edition/branch/master) +

+

A mod for Cities: Skylines that gives you more control over road and rail traffic in your city

+

Steam WorkshopDiscord GuildInstallation GuideUser GuideIssue TrackerReport a Bug

+

-A mod for **Cities: Skylines** that gives you more control over road and rail traffic in your city. +## Current release -[Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=583429740) • [Discord Guild](https://discord.gg/faKUnST) • [Installation](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/wiki/Installation) • [User Guide](http://www.viathinksoft.de/tmpe/wiki) • [Issue Tracker](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues) • [Report a Bug](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/wiki/Report-a-Bug) +> Having problems with traffic despawning after updating roads or rails? Try [Broken Node Detector](https://steamcommunity.com/sharedfiles/filedetails/?id=1777173984) which helps detect a game bug. Collossal Order are aware of the issue. -> Users having problems with traffic despawning after updating roads or rails are advised to try the [Broken Node Detector](https://steamcommunity.com/sharedfiles/filedetails/?id=1777173984) which helps detect a game bug. Co are aware of the issue. - -# Features - -* Timed traffic lights -* Change lane arrows -* Edit lane connections -* Add priority signs -* Junction restrictions - * Toggle u-turns - * Allow "turn on red" - * Enter blocked junctions - * Toggle pedestrian crossings -* Vehicle restrictions - * For roads and trains! -* Customise speed limits - * In MPH or km/h -* Toggle despawn -* Clear traffic, stuck cims, etc. - -# Changelog -### [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), ??/07/2019 +#### Version [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), ??/07/2019 * Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) * Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) * Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) @@ -59,22 +41,15 @@ A mod for **Cities: Skylines** that gives you more control over road and rail tr See [Full Changelog](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/blob/master/CHANGELOG.md) for details of earlier releases. -# Contributing - -We welcome contributions from the community! - -Contact us: +## Contributing -* [Issue tracker](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues) -* [Discord (chat)](https://discord.gg/faKUnST) -* [Steam Workshop (Stable)](https://steamcommunity.com/sharedfiles/filedetails/?id=583429740) -* [Steam Workshop (Labs)](https://steamcommunity.com/sharedfiles/filedetails/?id=1637663252) +We welcome contributions from the community! See: [Contributing Guide](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/wiki/Contributing) -# License +## License [MIT License](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/blob/master/LICENSE) (open source) -# Notice +## Support Policy The TM:PE team is happy to support you if you have any issues with the mod under the following conditions: - You are using the latest version of the STABLE and/or LABS mod. From 554bb9a97ae853ff9a833c2770c9a069f824791c Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 27 Jun 2019 01:41:59 +0100 Subject: [PATCH 095/142] Added draft of 10.21 to changelog, added heading styles --- CHANGELOG.md | 280 ++++++++++++++++++++++++++++----------------------- 1 file changed, 156 insertions(+), 124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bdb514a7..47c349818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,44 @@ # Cities: Skylines - Traffic Manager: *President Edition* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) # Changelog -10.20, 21/05/2019 +### [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), ??/07/2019 +- Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) +- Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) +- Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) +- Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) +- Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) +- Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid!) (#362) +- Fixed: Mail trucks ignoring lane arrows (#307, #338) +- Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) +- Fixed: Random parking broken (thanks s2500111 for beta testing) (#259, #359) +- Fixed: Pedestrian crossing restriction affects road-side parking (#259, #359) +- Fixed: 'Vanilla Trees Remover' is now compatible (thanks TPB for fixing) (#331, #332) +- Fixed: Single-lane bunching on DLS higher than 50% (#263 #334) +- Fixed: Lane changes at toll booths (also notified CO of bug in vanilla) (#225, #355) +- Fixed: Minor issues regarding saving/loading junction restrictions (#358) +- Fixed: Changes of default junction restrictions not reflected in UI overlay (#358) +- Fixed: Resetting stuck cims unpauses the simulation (#358, #351) +- Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190) +- Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +- Updated: Chinese translation (thanks Emphasia) (#375, #336) +- Updated: German translation (thanks kvakvs) (#384) +- Updated: Polish translation (thanks krzychu124) (#384, #333) +- Updated: Russian translation (thanks vitalii201) (#327, #328) +- Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) +- Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) +- Meta: Pathfinder debug logging tools (switch `26`) (#370) +- Meta: Separate binaries for Stable and Labs on GitHub release pages (#360) +- Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) +- Meta: Added GitHub issue templates for bugs, features, translations. (#272) +- Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) +- Meta: Added entire `.vs/` folder to `.gitignore` (#395) + +### 10.20, 21/05/2019 - Updated for game version 1.12.0-f5 - Updated Korean translation (thanks Twotoolus-FLY-LShst) (#294) - Updated French translation (thanks PierreTSE) (#311) -10.19, 20/04/2019 +### 10.19, 20/04/2019 - Bugfix: Mod options overlapping issue (#250, #266). - Added: Japanese language (thanks mashitaro) (#258). - Update: Chinese language (thanks Emphasia) (#285, #286). @@ -14,12 +46,12 @@ - Update: Moved "Delete" step button on timed traffic lights (#283, #285). - Update: Mod incompatibility checker can now be disabled, or skip disabled mods (#264, #284, #286). -10.18, 29/03/2019 +### 10.18, 29/03/2019 - Bugfix: Parking AI: Cars do not spawn at outside connections (#245) - Bugfix: Trams perform turns on red (#248) - Update: Service Radius Adjuster mod by Egi removed from incompatible mods list (#255) -10.17, 23/03/2019 +### 10.17, 23/03/2019 - Introduced new versioning scheme (10.17 instead of 1.10.17) - Synchronized code and version with stable version - Updated russian translation (thanks to @vitalii201 for translating) (#207) @@ -38,7 +70,7 @@ - Bugfix: Citizen not found errors when using walking tours (#219) - Bugfix: Timed light indicator only visible when any timed light node is selected (#222) -1.10.16, 24/02/2019 +### 1.10.16, 24/02/2019 - Gameplay: Fixed problem with vehicle despawn after road upgrade/remove (thanks @pcfantasy for implementation suggestion)(#86, #101) - Gameplay: Fixed problem with vehicles unable to choose lane when u-turn at dead-end (thanks @pcfantasy for implementation and @aubergine10 for neccesary tests)(#101) - Gameplay: Fixed problem when user couldn't change state of 'Turn on Red' while enabled_by_default option not selected (thanks @Sp3ctre18 for bug confirmation) (#102) @@ -50,7 +82,7 @@ - Other: Fixed 'silent error' inside log related with "Esc key handler" (#92) - Contribution: Added project building instructions and PR review -1.10.15, 10/02/2019 +### 1.10.15, 10/02/2019 - Enhancement: Now you can use Escape key to close Traffic Manager without returning to Pause Menu (thanks to @aubergine10 for suggestion) (#16) - Gameplay: Updated pathfinding with missing vanilla logic - Gameplay: Tweaked values in CargoTruckAI path finding (thanks to @pcfantasy for improvement suggestion) @@ -60,7 +92,7 @@ - Other: Added notification if user is still subscribed to old original TM:PE - [Experimental feature] Turn on red (thanks to @FireController1847 for implementation and to @pcfantasy for source code base) -1.10.14, 27/01/2019 +### 1.10.14, 27/01/2019 - Bugfix: Added missing Car AI type (postVanAI) - now post vans and post trucks are assigned to service vehicles group - Bugfix: Vehicles doesn't stop when driving through toll booth - fixes toll booth income too - Bugfix: Cargo Airport doesn't work (Cargo planes not spawning and not arriving) @@ -68,10 +100,10 @@ - Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating) - Fixed Mod Options layout (text label overlaps slider control if too wide) -1.10.13, 31/10/2018 +### 1.10.13, 31/10/2018 - Bugfix: Tollbooth fix -1.10.12, 08/12/2018 +### 1.10.12, 08/12/2018 - Added the option to allow/disallow vehicles to enter a blocked junction at transition and pedestrian crossing nodes (#195) - Updated Russian translation (thanks to vitalii2011 for translating) - Bent nodes do not allow for u-turns by default (#170) @@ -80,7 +112,7 @@ - Bugfix: Parking AI: Cims leaving the city despawn their car at public transport stations (#214) - Bugfix: Crossing restrictions do not work at intersection between road and highway (#212) -1.10.11, 21/07/2018 +### 1.10.11, 21/07/2018 - U-turn lane connections are represented by appropriate lane arrow (#201) - Bugfix: Heavy vehicles are unable to u-turn at dead ends (#194) - Bugfix: Routing & Priority rules do not work properly for acute (< 30°)/obtuse(> 150°) segment angles (#199) @@ -88,18 +120,18 @@ - Bugfix: Race condition in path-finding might cause paths to be assigned to wrong vehicle/citizen (#205) - Bugfix: Vehicles are unable to perform u-turns when setting off on multi-lane roads (#197) -1.10.10, 14/07/2018 +### 1.10.10, 14/07/2018 - Parking AI: Improved park & ride behavior - Parking AI: Walking paths from parking position to destination building take public transportation into account - Bugfix: Parking AI causes unnecessary path-findings (#183, thanks to Sipke82 for reporting) - Bugfix: Prohibiting cims from crossing the road also affect paths where crossing is unnecessary (#168, thanks to aubergine10 for reporting) -1.10.9, 13/07/2018 +### 1.10.9, 13/07/2018 - Updated for game version 1.10.1-f3 - Re-implemented path-finding algorithm - Updated French translation (thanks to mjm92150 for translating!) -1.10.8, 01/07/2018 +### 1.10.8, 01/07/2018 - Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating) - Updated Polish translation (thanks to @Krzychu1245 for translating) - Added button to remove parked vehicles (in options dialog, see maintenance tab) @@ -111,13 +143,13 @@ - Bugfix: Park maintenance vehicles are not recognized as service vehicles - Bugfix: Cars leaving city state "thinking of a good parking spot" (thanks to @aubergine10 for reporting) -1.10.7, 28/05/2018 +### 1.10.7, 28/05/2018 - Bugfix: U-turn routing is inconsistent on transport lines vs. bus paths (#137, thanks to @Zorgoth for reporting this issue) - Bugfix: Junction restrictions for pedestrian crossings are sometimes not preserved (#142, thanks to Anrew and @wizardrazer for reporting this issue) - Fixed: Geometry subscription feature may cause performance issues (#145) - Fixed: Parking AI: Transport mode storage causes performance issues during loading (#147, thanks to @hannebambel002 and @oneeyets for reporting and further for providing logs and savegames) -1.10.6, 24/05/2018 +### 1.10.6, 24/05/2018 - Updated for game version 1.10.0-f3 - Accessibility: New option: Main menu size can be controlled - Accessibility: New option: GUI and overlay transparency can be controlled @@ -141,7 +173,7 @@ - Bugfix: Allowing/Disallowing vehicles to enter a blocked junction does not work for certain junctions - Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating) -1.10.5, 06/01/2018 +### 1.10.5, 06/01/2018 - UI scaling removed - Simplified Chinese translation updated (thanks to Emphasia for translating) - Polish translation updated (thanks to @Krzychu1245 for translating) @@ -160,28 +192,28 @@ - Bugfix: Disabling tutorial message has no effect - Bugfix: "Stay on lane" feature does not work as intended for certain nodes -1.10.4, 19/10/2017 +### 1.10.4, 19/10/2017 - Updated for game version 1.9.0-f5 - Added possibility to add priority signs at multiple junctions at once (press Shift) - Added tutorials (can be disabled in the options window globally) -1.10.3, 18/08/2017 +### 1.10.3, 18/08/2017 - Bugfix: Setting unlimited speed limit causes vehicles to crawl at low speed (thanks to @sethisuwan for reporting this issue) - Bugfix: Vehicle-separated traffic lights do not show up for trams & monorails (thanks to @thecitiesdork for reporting this issue) -1.10.2, 17/08/2017 +### 1.10.2, 17/08/2017 - Updated for game version 1.8.0-f3 - Improved performance - Bugfix: Pedestrians sometimes ignore red traffic light signals (thanks to @(c)RIKUPI™ for reporting this issue) - Bugfix: Timed traffic lights do not correctly recognize set vehicle restrictions (thanks to @alborzka for reporting this issue) -1.10.1, 05/08/2017 +### 1.10.1, 05/08/2017 - Updated Polish, Korean, and Simplified Chinese translations - Bugfix: Default routing is disabled if the lane connector is used on a subset of all available lanes only - Bugfix: Parking AI cannot be enabled/disabled - Bugfix: Lane connection points can connected to themselves -1.10.0, 30/07/2017 +### 1.10.0, 30/07/2017 - New feature: Dynamic Lane Selection - New feature: Adaptive step switching - New feature: Individual vehicles may be removed from the game @@ -219,13 +251,13 @@ - Bugfix: Vehicles may get stuck in several situations - Upgrading to a road with bus lanes now copies an already existing traffic light state to the new traffic light -1.9.6, 28/05/2017 +### 1.9.6, 28/05/2017 - Updated Simplified Chinese translation - Bugfix: Vehicles cannot perform u-turns at junctions with only one outgoing segment (thanks to @Sunbird for reporting this issue) - Bugfix: Path-finding costs for large distances exceed the maximum allowed value (thanks to @Huitsi for reporting this issue) - Bugfix: Under certain circumstances path-finding at railroad crossings allow switching from road to rail tracks. -1.9.5, 24/05/2017 +### 1.9.5, 24/05/2017 - Updated for game version 1.7.1-f1 - Updated Polish, Korean and Italian translation - Language can now be switched without requiring a game restart @@ -235,7 +267,7 @@ - Bugfix: Upgrading a train track segment next to a timed traffic light causes trains to ignore the traffic light - Hotfix: Cable cars despawn at end-of-line stations -1.9.4, 23/05/2017 +### 1.9.4, 23/05/2017 - New option: Ban private cars and trucks on bus lanes - Updated Spanish and French translation - Optimized path-finding @@ -243,7 +275,7 @@ - Increased path-finding cost for disregarding vehicle restrictions - Bugfix: Path-finding is unable to calculate certain paths after modifying the road network -1.9.3, 22/05/2017 +### 1.9.3, 22/05/2017 - Disabled notification of route recalculating because some players report crashes - Removed default vehicle restrictions from bus lanes - Modified junction restrictions come into effect instantaneously @@ -253,16 +285,16 @@ - Bugfix: Under certain circumstances priority signs cannot be removed - Bugfix: Path-finding is unable to calculate certain paths -1.9.2, 20/05/2017 +### 1.9.2, 20/05/2017 - UI: Main menu & UI tools performance improved - Bugfix: Traffic lights can be removed from junctions that are controlled by a timed traffic light program -1.9.1, 19/05/2017 +### 1.9.1, 19/05/2017 - Updated French, Dutch and Korean translation - Bugfix: Using the vanilla traffic light toggling feature crashes the game if TMPE's main menu has not been opened at least once - Bugfix: AI: More car traffic and less public transportation present than in vanilla -1.9.0, 18/05/2017 +### 1.9.0, 18/05/2017 - Updated for game version 1.7.0-f5 - New feature: Parking restrictions - New feature: Speed limits can be set up for individual lanes with the Control key @@ -291,17 +323,17 @@ - Bugfix: Vehicle restriction and speed limit signs overlay is displayed on the wrong side of inverted road segments - Bugfix: Influx statistics value is zero (thanks to @hjo for reporting this issue) -1.8.16, 20/03/2017 +### 1.8.16, 20/03/2017 - Lane connections can now also be removed by pressing the backspace key - Improved lane selection for busses if the option "Busses may ignore lane arrows" is activated - Bugfix: The game sometimes freezes when using the timed traffic light tool - Bugfix: Lane connections are not correctly removed after modifying/removing a junction - Bugfix: Selecting a junction for setting up junction restrictions toggles the currently hovered junction restriction icon -1.8.15, 27/01/2017 +### 1.8.15, 27/01/2017 - Updated for game version 1.6.3-f1 -1.8.14, 07/01/2017 +### 1.8.14, 07/01/2017 - Bugfix: Wait/flow ratio at timed traffic lights is sometimes not correctly calculated - Bugfix: A deadlock situation can arise at junctions with priority signs such that no vehicle enters the junction - Bugfix: When adding a junction to a timed traffic light, sometimes light states given by user input are not correctly stored @@ -311,26 +343,26 @@ - Tram lanes can now be customized by using the lane connector tool - Minor performance optimizations for priority sign simulation -1.8.13, 05/01/2017 +### 1.8.13, 05/01/2017 - Bugfix: Timed traffic ligt data can become corrupt when upgrading a road segment next to a traffic light, leading to faulty UI behavior (thanks to @Brain for reporting this issue) - Bugfix: The position of the main menu button resets after switching to the free camera mode (thanks to @Impact and @gravage for reporting this issue) - Bugfix: A division by zero exception can occur when calculating the average number of waiting/floating vehicles - Improved selection of overlay markers on underground roads (thanks to @Padi for reminding me of that issue) - Minor performance improvements -1.8.12, 02/01/2017 +### 1.8.12, 02/01/2017 - Updated for game version 1.6.2-f1 - Bugfix: After leaving the "Manual traffic lights" mode the traffic light simulation is not cleaned up correctly (thanks to @diezelunderwood for reporting this issue) - Bugfix: Insufficient access rights to log file causes the mod to crash -1.8.11, 02/01/2017 +### 1.8.11, 02/01/2017 - Bugfix: Speed limits for elevated/underground road segments are sometimes not correctly loaded (thanks to @Pirazel and @[P.A.N] Uf0 for reporting this issue) -1.8.10, 31/12/2016 +### 1.8.10, 31/12/2016 - Improved path-finding performance (a bit) - Added a check for invalid road thumbnails in the "custom default speed limits" dialog -1.8.9, 29/12/2016 +### 1.8.9, 29/12/2016 - It is now possible to set speed limits for metro tracks - Custom default speed limits may now be defined for train and metro tracks - Junction restrictions may now be controlled at bend road segments @@ -340,12 +372,12 @@ - Bugfix: Selecting a junction to set up priority signs sometimes does not work (thanks to @Artemis *Seven* for reporting this issue) - Bugfix: Automatic pedestrian lights do not work as expected at junctions with incoming one-ways and on left-hand traffic maps -1.8.8, 25/12/2016 +### 1.8.8, 25/12/2016 - Bugfix: Taxis are not being used - Bugfix: Prohibiting u-turns with the junction restriction tool does not work (thanks to @Kisoe for reporting this issue) - Bugfix: Cars are sometimes floating across the map while trying to park (thanks to @[Delta ²k5] for reporting this issue) -1.8.7, 24/12/2016 +### 1.8.7, 24/12/2016 - Bugfix: Parking AI: Cims that try to reach their parked car are sometimes teleported to another location where they start to fly through the map in order to reach their car - Bugfix: Parking AI: Cims owning a parked car do not consider using other means of transportation - Bugfix: Parking AI: Residents are unable to leave the city through a highway outside connection @@ -361,16 +393,16 @@ - Updated French translation (thanks to @simon.royer007 for translating) - Added Italian translation (thanks to @Admix for translating) -1.8.6, 12/12/2016 +### 1.8.6, 12/12/2016 - Added Korean language (thanks to @Toothless FLY [ROK]LSh.st for translating) - Updated Chinese language code (zh-cn -> zh) in order to make it compatible with the game (thanks to @Lost丶青柠 for reporting this issue) -1.8.5, 11/12/2016 +### 1.8.5, 11/12/2016 - Updated to game version 1.6.1-f2 - Removed option "Evacuation busses may only be used to reach a shelter" (CO fixed this issue) - Bugfix: Average speed limits are not correctly calculated for road segments with bicycle lanes (thanks to @Toothless FLY [ROK]LSh.st for reporting this issue) -1.8.4, 11/12/2016 +### 1.8.4, 11/12/2016 - New feature: "Stay on lane": By pressing Shift + S in the Lane Connector tool you can now link connected lanes such that vehicles are not allowed to change lanes at this point. Press Shift + S again to restrict "stay on lane" to either road direction. - U-turns are now only allowed to be performed from the innermost lane - TMPE now detects if the number of spawned vehicles is reaching its limit (16384). If so, spawning of service/emergency vehicles is prioritized over spawning other vehicles. @@ -381,21 +413,21 @@ - Bugfix: While path-finding is in progress vehicles do "bungee-jumping" on the current segment (thanks to @mxolsenx, @Howzitworld for reporting this issue) - Bugfix: Cims leaving the city search for parking spaces near the outside connection which is obviously not required -1.8.3, 4/12/2016 +### 1.8.3, 4/12/2016 - Bugfix: Despite having the Parking AI activated, cims sometimes still spawn pocket cars. - Bugfix: When the Parking AI is active, bicycle lanes are not used (thanks to @informmanuel for reporting this issue) - Tweaked u-turn behavior - Improved info views -1.8.2, 3/12/2016 +### 1.8.2, 3/12/2016 - Bugfix: Taxis were not used (thanks to @[Delta ²k5] for reporting) - Bugfix: Minor UI fix in Default speed limits dialog -1.8.1, 1/12/2016 +### 1.8.1, 1/12/2016 - Updated translations: Polish, Chinese (simplified) - Bugfix: Mod crashed when loading a second savegame -1.8.0, 29/11/2016 +### 1.8.0, 29/11/2016 - Updated to game version 1.6.0-f4 - New feature: Default speed limits - New feature: Parking AI (replaces "Prohibit cims from spawning pocket cars") @@ -414,29 +446,29 @@ - Removed compatibility check for Traffic++ V2 (Traffic++ V2 is no longer compatible with TMPE because maintaining compatibility is no longer feasible due to the high effort) - Updated translations: German, Portuguese, Russian, Dutch, Chinese (traditional) -1.7.15, 26/10/2016 +### 1.7.15, 26/10/2016 - Bugfix: Timed traffic lights window disappears when clicking on it with the middle mouse button (thanks to @Nexus and @Mariobro14 for helping me identifying the cause of this bug) -1.7.14, 18/10/2016 +### 1.7.14, 18/10/2016 - Updated for game version 1.5.2-f3 -1.7.13, 15/09/2016 +### 1.7.13, 15/09/2016 - Implemented a permanent fix to solve problems with stuck vehicles/cims caused by third party mods - Added a button to reset stuck vehicles/cims (see mod settings menu) - AI: Improved lane selection algorithm - Bugfix: AI: Lane merging was not working as expected - Bugfix: Pedestrian light states were sometimes not being stored correctly (thanks to Filip for pointing out this problem) -1.7.12, 09/09/2016 +### 1.7.12, 09/09/2016 - AI: Lane changes are reduced on congested road segments - Timed traffic lights should now correctly detect trains and trams - Bugfix: GUI: Junction restriction icons sometimes disappear - Updated Chinese (simplified) translation -1.7.11, 01/09/2016 +### 1.7.11, 01/09/2016 - Updated to game version 1.5.1-f3 -1.7.10, 31/08/2016 +### 1.7.10, 31/08/2016 - Players can now disable spawning of pocket cars - Updated Chinese (simplified) translation - Bugfix: Timed traffic lights were flickering @@ -446,18 +478,18 @@ - Bugfix: Manual pedestrian traffic light states were not correctly handled - Bugfix: Junction restrictions overlay did not show all restricted junctions -1.7.9, 22/08/2016 +### 1.7.9, 22/08/2016 - In-game traffic light states are now correctly rendered when showing "yellow" - Removed negative effects on public transport usage - GUI: Traffic light states do not flicker anymore - Performance improvements -1.7.8, 18/08/2016: +### 1.7.8, 18/08/2016: - Bugfix: Cims sometimes got stuck (thanks to all reports and especially to @Thilawyn for providing a savegame) - GUI: Improved traffic light arrow display - Improved performance while saving -1.7.7, 16/08/2016: +### 1.7.7, 16/08/2016: - AI: Instead of walking long distances, citizens now use a car - AI: Citizens will remember their last used mode of transport (e.g. they will not drive to work and come return by bus anymore) - AI: Increased path-finding costs for traversing over restricted road segments @@ -466,7 +498,7 @@ - GUI: Improved window scaling on lower resolutions - Improved performance while saving -1.7.6, 14/08/2016: +### 1.7.6, 14/08/2016: - New feature: Players may now prohibit cims from crossing the street - AI: Tuned randomization of lane changing behavior - AI: Introduced path-finding costs for leaving main highway (should reduce amount of detours taken) @@ -479,13 +511,13 @@ - Bugfix: Vehicles were endlessly waiting for each other at junctions with certain priority sign configurations - Bugfix: AI: Lane changing costs corrected -1.7.5, 07/08/2016: +### 1.7.5, 07/08/2016: - Bugfix: AI: Cims were using pocket cars whenever possible - Bugfix: AI: Path-finding failures led to much less vehicles spawning - Bugfix: AI: Lane selection at junctions with custom lane connection was not always working properly (e.g. for Network Extensions roads with middle lane) - Bugfix: While editing a timed traffic light it could happen that the traffic light was deleted -1.7.4, 31/07/2016: +### 1.7.4, 31/07/2016: - AI: Switched from relative to absolute traffic density measurement - AI: Tuned new parameters - Bugfix: Activated/Disabled features were not loaded correctly @@ -494,21 +526,21 @@ - Code improvements - Added French translations (thanks to @simon.royer007 for translating!) -1.7.3, 29/07/2016: +### 1.7.3, 29/07/2016: - Added the ability to enable/disable mod features (e.g. for performance reasons) - Bugfix: Vehicle type determination was incorrect (fixed u-turning trams/trains, stuck vehicles) - Bugfix: Clicking on a train/tram node with the lane connector tool led to an uncorrectable error (thanks to @noaccount for reporting this problem) - Further code improvements -1.7.2, 26/07/2016: +### 1.7.2, 26/07/2016: - Optimized UI overlay performance -1.7.1, 24/07/2016: +### 1.7.1, 24/07/2016: - Reverted "Busses now may only ignore lane arrows if driving on a bus lane" for now - Bugfix: Trains were not despawning if no path could be calculated - Workaround for third-party issue: TM:PE now detects if the calculation of total vehicle length fails -1.7.0, 23/07/2016: +### 1.7.0, 23/07/2016: - New feature: Traffic++ lane connector - Busses now may only ignore lane arrows if driving on a bus lane - Rewritten and simplified vehicle position tracking near timed traffic lights and priority signs for performance reasons @@ -526,12 +558,12 @@ - Bugfix: AI: Highway rules on left-hand traffic maps did not work the same as on right-hand traffic maps - Bugfix: Upgrading a road segment next to a timed traffic light removed the traffic light leading to an inconsistent state (thanks to @ad.vissers for pointing out this problem) -1.6.22, 29/06/2016: +### 1.6.22, 29/06/2016: - AI: Taxis now may not ignore lane arrows and are using bus lanes whenever possible (thanks to @Cochy for pointing out this issue) - AI: Busses may only ignore lane arrows while driving on a bus lane - Bugfix: Traffic measurement at timed traffic lights was incorrect -1.6.22, 21/06/2016: +### 1.6.22, 21/06/2016: - Speed/vehicle restrictions may now be applied to all road segments between two junctions by holding the shift key - Reworked how changes in the road network are recognized - Advanced Vehicle AI: Improved lane selection at junctions where bus lanes end @@ -546,39 +578,39 @@ - Bugfix: Trains/Trams were sometimes ignoring timed traffic lights (many thanks to Filip for identifying this problem) - Bugfix: Building roads with bus lanes caused garbage, bodies, etc. to pile up -1.6.21, 14/06/2016: +### 1.6.21, 14/06/2016: - Bugfix: Too few cargo trains were spawning (thanks to @Scratch, @toruk_makto1, @Mr.Miyagi, @mottoh and @Syparo for pointing out this problem) - Bugfix: Vehicle restrictions did not work as expected (thanks to @nordlaser for pointing out this problem) -1.6.20, 11/06/2016: +### 1.6.20, 11/06/2016: - Bugfix: Priority signs were not working correctly (thanks to @mottoth, @Madgemade for pointing out this problem) -1.6.19, 11/06/2016 +### 1.6.19, 11/06/2016 - Bugfix: Timed traffic lights UI not working as expected (thanks to @Madgemade for pointing out this problem) -1.6.18, 09/06/2016 +### 1.6.18, 09/06/2016 - Updated for game patch 1.5.0-f4 - Improved performance of priority signs and timed traffic lights - Players can now select elevated rail segments/nodes - Trams and trains now follow priority signs - Improved UI behavior when setting up priority signs -1.6.17, 20/04/2016 +### 1.6.17, 20/04/2016 - Hotfix for reported path-finding problems -1.6.16, 19/04/2016 +### 1.6.16, 19/04/2016 - Updated for game patch 1.4.1-f2 -1.6.15, 22/03/2016 +### 1.6.15, 22/03/2016 - Updated for game path 1.4.0-f3 - Possible fix for crashes described by @cosminel1982 - Added traditional Chinese translation -1.6.14, 17/03/2016 +### 1.6.14, 17/03/2016 - Bugfix: Cargo trucks did not obey vehicle restrictions (thanks to @ad.vissers for pointing out this problem) - Bugfix: When Advanced AI was deactivated, u-turns did not have costs assigned -1.6.13, 16/03/2016 +### 1.6.13, 16/03/2016 - Added Dutch translation - The pedestrian light mode of a traffic light can now be switched back to automatic - Vehicles approaching a different speed limit change their speed more gradually @@ -588,31 +620,31 @@ - Bugfix: After loading another savegame, timed traffic lights stopped working for a certain time - Bugfix: Lane speed calculation corrected -1.6.12, 03/03/2016 +### 1.6.12, 03/03/2016 - Improved memory usage - Bugfix: Adding/removing junctions to/from existing timed traffic lights did not work (thanks to @nieksen for pointing out this problem) - Bugfix: Separate timed traffic lights were sometimes not saved (thanks to @nieksen for pointing out this problem) - Bugfix: Fixed an initialization error (thanks to @GordonDry for pointing out this problem) -1.6.11, 03/03/2016 +### 1.6.11, 03/03/2016 - Added Chinese translation - By pressing "Page up"/"Page down" you can now switch between traffic and default map view - Size of information icons and signs is now based on your screen resolution - UI code refactored -1.6.10, 02/03/2016 +### 1.6.10, 02/03/2016 - Additional controls for vehicle restrictions added - Bugfix: Clicking on a Traffic Manager overlay resulted in vanilla game components (e.g. houses, vehicles) being activated -1.6.9, 02/03/2016 +### 1.6.9, 02/03/2016 - Updated for game patch 1.3.2-f1 -1.6.8, 01/03/2016 +### 1.6.8, 01/03/2016 - Path-finding: Major performance improvements - Updated Japanese translation (thanks to @Akira Ishizaki for translating!) - Added Spanish translation -1.6.7, 27/02/2016 +### 1.6.7, 27/02/2016 - Tuned AI parameters - Improved traffic density measurements - Improved lane changing near junctions: Reintroduced costs for lane changing before junctions @@ -621,7 +653,7 @@ - Bugfix: U-turns did not have appropriate costs assigned - Bugfix: The time span between AI traffic measurements was too high -1.6.6, 27/02/2016 +### 1.6.6, 27/02/2016 - It should now be easier to select segment ends in order to change lane arrows. - Priority signs now cannot be setup at outgoing one-ways. - Updated French translation (thanks to @simon.royer007 for translating!) @@ -630,11 +662,11 @@ - Updated Russian translation (thanks to @FireGames for translating!) - Bugfix: U-turning vehicles were not obeying the correct directional traffic light (thanks to @t1a2l for pointing out this problem) -1.6.5, 24/02/2016 +### 1.6.5, 24/02/2016 - Added despawning setting to options dialog - Improved detection of Traffic++ V2 -1.6.4, 23/02/2016 +### 1.6.4, 23/02/2016 - Minor performance improvements - Bugfix: Path-finding calculated erroneous traffic density values - Bugfix: Cims left the bus just to hop on a bus of the same line again (thanks to @kamzik911 for pointing out this problem) @@ -643,23 +675,23 @@ - Bugfix: Default settings for vehicle restrictions on bus lanes corrected - Bugfix: Pedestrian lights at railway junctions fixed (they are still invisible but are derived from the car traffic light state automatically) -1.6.3, 22/02/2016 +### 1.6.3, 22/02/2016 - Bugfix: Using the "Old Town" policy led to vehicles not spawning. - Bugfix: Planes, cargo trains and ship were sometimes not arriving - Bugfix: Trams are not doing u-turns anymore -1.6.2, 20/02/2016 +### 1.6.2, 20/02/2016 - Trams are now obeying speed limits (thanks to @Clausewitz for pointing out the issue) - Bugfix: Clear traffic sometimes throwed an error - Bugfix: Vehicle restrctions did not work as expected (thanks to @[Delta ²k5] for pointing out this problem) - Bugfix: Transition of automatic pedestrian lights fixed -1.6.1, 20/02/2016 +### 1.6.1, 20/02/2016 - Improved performance - Bugfix: Fixed UI issues - Modifying mod options through the main menu now gives an annoying warning message instead of a blank page. -1.6.0, 18/02/2016 +### 1.6.0, 18/02/2016 - New feature: Separate traffic lights for different vehicle types - New feature: Vehicle restrictions - Snowfall compatibility @@ -680,7 +712,7 @@ - Bugfix: Vehicle detection at timed traffic lights did not work as expected - Bugfix: Not all valid traffic light arrow modes were reachable -1.5.2, 01/02/2016 +### 1.5.2, 01/02/2016 - Traffic lights may now be added to/removed from underground junctions - Traffic lights may now be setup at *some* points of railway tracks (there seems to be a game-internal bug that prevents selecting arbitrary railway nodes) - Display of priority signs, speed limits and timed traffic lights may now be toggled via the options dialog @@ -689,17 +721,17 @@ - Bugfix: Pedestrians were ignoring timed traffic lights (thanks to @Hannes8910 for pointing out this problem) - Bugfix: Sometimes speed limits were not saved (thanks to @cca_mikeman for pointing out this problem) -1.5.1, 31/01/2016 +### 1.5.1, 31/01/2016 - Trains are now following speed limits -1.5.0, 30/01/2016 +### 1.5.0, 30/01/2016 - New feature: Speed restrictions (as requested by @Gfurst) - AI: Parameters tuned - Code improvements - Lane arrow changer window is now positioned near the edited junction (as requested by @GordonDry) - Bugfix: Flowing/Waiting vehicles count corrected -1.4.9, 27/01/2016 +### 1.4.9, 27/01/2016 - Junctions may now be added to/removed from timed traffic lights after they are created - When viewing/moving a timed step, the displayed/moved step is now highlighted (thanks to Joe for this idea) - Performance improvements @@ -710,7 +742,7 @@ - Bugfix: Lane merging in left-hand traffic systems fixed - Bugfix: Turning priority roads fixed (thanks to @GordonDry for pointing out this problem) -1.4.8, 25/01/2016 +### 1.4.8, 25/01/2016 - AI: Parameters have been tuned - AI: Added traffic density measurements - Performance improvements @@ -721,28 +753,28 @@ - Bugfix: In highway rule mode, lane arrows sometimes flickered - Bugfix: Some traffic light arrows were sometimes not selectable -1.4.7, 22/01/2016 +### 1.4.7, 22/01/2016 - Added translation to Portuguese (thanks to @igordeeoliveira for working on this!) - Reduced mean size of files can become quite big (thanks to @GordonDry for reporting this problem) - Bugfix: Freight ships/trains were not coming in (thanks to @Mas71 and @clus for reporting this problem) - Bugfix: The toggle "Vehicles may enter blocked junctions" did not work properly (thanks for @exxonic for reporting this problem) - Bugfix: If a timed traffic light is being edited the segment geometry information is not updated (thanks to @GordonDry for reporting this problem) -1.4.6, 22/01/2016 +### 1.4.6, 22/01/2016 - Running average lane speeds are measured now - Minor fixes -1.4.5, 22/01/2016 +### 1.4.5, 22/01/2016 - The option "Vehicles may enter blocked junctions" may now be defined for each junction separately - Bugfix: A deadlock in the path-finding is fixed - Bugfix: Small timed light sensitivity values (< 0.1) were not saved correctly - Bugfix: Timed traffic lights were not working for some players - Refactored segment geometry calculation -1.4.4, 21/01/2016 +### 1.4.4, 21/01/2016 - Added localization support -1.4.3, 20/01/2016 +### 1.4.3, 20/01/2016 - Several performance improvements - Improved calculation of segment geometries - Improved load balancing @@ -752,7 +784,7 @@ - Bugfix: Fixed an error in path-finding cost calculation - Bugfix: Outgoing roads were treated as ingoing roads when highway rules were activated -1.4.2, 16/01/2016 +### 1.4.2, 16/01/2016 - Several major performance improvements (thanks to @sci302 for pointing out those issues) - Improved the way traffic lights are saved/loaded - Lane-wise traffic density is only measured if Advanced AI is activated @@ -762,16 +794,16 @@ - Bugfix: Stop/Yield signs were not working properly (thanks to @GordonDry, @Glowstrontium for pointing out this problem) - Bugfix: Cargo trucks were ignoring the "Heavy ban" policy, they should do now (thanks to @Scratch for pointing out this problem) -1.4.1, 15/01/2016 +### 1.4.1, 15/01/2016 - Bugfix: Path-finding near junctions fixed -1.4.0, 15/01/2016 +### 1.4.0, 15/01/2016 - Introducing Advanced Vehicle AI (disabled by default! Go to "Options" and enable it if you want to use it.) - Bugfix: Traffic lights were popping up in the middle of roads - Bugfix: Fixed the lane changer for left-hand traffic systems (thanks to @Phishie for pointing out this problem) - Bugfix: Traffic lights on invalid nodes are not saved anymore -1.3.24, 13/01/2016 +### 1.3.24, 13/01/2016 - Improved handling of priority signs - Priority signs: After adding two main road signs the next offered sign is a yield sign - Priority signs: Vehicles now should notice earlier that they can enter a junction @@ -785,113 +817,113 @@ - Bugfix: If reckless driving was set to "The Holy City (0 %)", vehicles blocked intersections with traffic light. - Bugfix: Traffic light arrow modes were sometimes not correctly saved -1.3.23, 09/01/2016 +### 1.3.23, 09/01/2016 - Bugfix: Corrected an issue where toggled traffic lights would not be saved/loaded correctly (thanks to @Jeffrios and @AOD_War_2g for pointing out this problem) - Option added to forget all toggled traffic lights -1.3.22, 08/01/2016 +### 1.3.22, 08/01/2016 - Added an option allowing busses to ignore lane arrows - Added an option to display nodes and segments -1.3.21, 06/01/2016 +### 1.3.21, 06/01/2016 - New feature: Traffic Sensitivity Tuning - UI improvements: When adding a new step to a timed traffic light the lights are inverted. - Timed traffic light status symbols should now be less annoying - Bugfix: Deletion of junctions that were members of a traffic light group is now handled correctly -1.3.20, 04/01/2016 +### 1.3.20, 04/01/2016 - Bugfix: Timed traffic lights are not saved correctly after upgrading a road nearby - UI improvements - New feature: Reckless driving -1.3.19, 04/01/2016 +### 1.3.19, 04/01/2016 - Timed traffic lights: Absolute minimum time changed to 1 - Timed traffic lights: Velocity of vehicles is being measured to detect traffic jams - Improved traffic flow measurement - Improved path finding: Cims may now choose their lanes more independently - Bugfix: Upgrading a road resets the traffic light arrow mode -1.3.18, 03/01/2016 +### 1.3.18, 03/01/2016 - Provided a fix for unconnected junctions caused by other mods - Crosswalk feature removed. If you need to add/remove crosswalks please use the "Crossings" mod. - UI improvements: You can now switch between activated timed traffic lights without clicking on the menu button again -1.3.17, 03/01/2016 +### 1.3.17, 03/01/2016 - Bugfix: Timed traffic lights cannot be added again after removal, toggling traffic lights does not work (thanks to @Fabrice, @ChakyHH, @sensual.heathen for pointing out this problem) - Bugfix: After using the "Manual traffic lights" option, toggling lights does not work (thanks to @Timso113 for pointing out this problem) -1.3.16, 03/01/2016 +### 1.3.16, 03/01/2016 - Bugfix: Traffic light settings on roads of the Network Extensions mods are not saved (thanks to @Scarface, @martintech and @Sonic for pointing out this problem) - Improved save data management -1.3.15, 02/01/2016 +### 1.3.15, 02/01/2016 - Simulation accuracy (and thus performance) is now controllable through the game options dialog - Bugfix: Vehicles on a priority road sometimes stop without an obvious reason -1.3.14, 01/01/2016 +### 1.3.14, 01/01/2016 - Improved performance - UI: Non-timed traffic lights are now automatically removed when adding priority signs to a junction - Adjusted the adaptive traffic light decision formula (vehicle lengths are considered now) - Traffic two road segments in front of a timed traffic light is being measured now -1.3.13, 01/01/2016 +### 1.3.13, 01/01/2016 - Bugfix: Lane arrows are not correctly translated into path finding decisions (thanks to @bvoice360 for pointing out this problem) - Bugfix: Priority signs are sometimes undeletable (thank to @Blackwolf for pointing out this problem) - Bugfix: Errors occur when other mods without namespace definitions are loaded (thanks to @Arch Angel for pointing out this problem) - Connecting a new road segment to a junction that already has priority signs now allows modification of the new priority sign -1.3.12, 30/12/2015 +### 1.3.12, 30/12/2015 - Bugfix: Priority signs are not editable (thanks to @ningcaohan for pointing out this problem) -1.3.11, 30/12/2015 +### 1.3.11, 30/12/2015 - Road segments next to a timed traffic light may now be deleted/upgraded/added without leading to deletion of the light - Priority signs and Timed traffic light state symbols are now visible as soon as the menu is opened -1.3.10, 29/12/2015 +### 1.3.10, 29/12/2015 - Fixed an issue where timed traffic light groups were not deleted after deleting an adjacent segment -1.3.9, 29/12/2015 +### 1.3.9, 29/12/2015 - Introduced information icons for timed traffic lights - Mod is now compatible with "Improved AI" (Lane changer is deactivated if "Improved AI" is active) -1.3.8, 29/12/2015 +### 1.3.8, 29/12/2015 - Articulated busses are now simulated correctly (thanks to @nieksen for pointing out this problem) - UI improvements -1.3.7, 28/12/2015 +### 1.3.7, 28/12/2015 - When setting up a new timed traffic light, yellow lights from the real-world state are not taken over - When loading another save game via the escape menu, Traffic Manager does not crash - When loading another save game via the escape menu, Traffic++ detection works as intended - Lane arrows are saved correctly -1.3.6, 28/12/2015 +### 1.3.6, 28/12/2015 - Bugfix: wrong flow value taken when comparing flowing vehicles - Forced node rendering after modifying a crosswalk -1.3.5, 28/12/2015 +### 1.3.5, 28/12/2015 - Fixed pedestrian traffic Lights (thanks to @Glowstrontium for pointing out this problem) - Better fix for: Deleting a segment with a timed traffic light does not cause a NullReferenceException - Adjusted the comparison between flowing (green light) and waiting (red light) traffic -1.3.4, 27/12/2015 +### 1.3.4, 27/12/2015 - Better traffic jam handling -1.3.3, 27/12/2015 +### 1.3.3, 27/12/2015 - (Temporary) hotfix: Deleting a segment with a timed traffic light does not cause a NullReferenceException - If priority signs are located behind the camera they are not rendered anymore -1.3.2, 27/12/2015 +### 1.3.2, 27/12/2015 - Priority signs are persistently visible when Traffic Manager is in "Add priority sign" mode - Synchronized traffic light rendering: In-game Traffic lights display the correct color (Thanks to @Fabrice for pointing out this problem) - Traffic lights switch between green, yellow and red. Not only between green and red. - UI tool tips are more explanatory and are shown longer. -1.3.1, 26/12/2015 +### 1.3.1, 26/12/2015 - Minimum time units may be zero now - Timed traffic lights of deleted/modified junctions get properly disposed -1.3.0, 25/12/2015 +### 1.3.0, 25/12/2015 - **Adaptive Timed Traffic Lights** (automatically adjusted based on traffic amount) -1.2.0 (iMarbot) +### 1.2.0 (iMarbot) - Updated for 1.2.2-f2 game patch. From 28407b0a281dd0c5bab6c48b743c04942cd87d6d Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Thu, 27 Jun 2019 01:56:42 +0100 Subject: [PATCH 096/142] Changelog update: basic offline support for Origin users --- CHANGELOG.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c349818..d2287049f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) - Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) - Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) +- Added: Basic support of offline mode for users playing on EA's Origin service (#333) - Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) - Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid!) (#362) - Fixed: Mail trucks ignoring lane arrows (#307, #338) @@ -32,6 +33,7 @@ - Meta: Added GitHub issue templates for bugs, features, translations. (#272) - Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) - Meta: Added entire `.vs/` folder to `.gitignore` (#395) +* Meta: Updated install guide to include section for EA Origin users (#333) ### 10.20, 21/05/2019 - Updated for game version 1.12.0-f5 diff --git a/README.md b/README.md index bfc53d58e..f14b11d9e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ * Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) * Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) * Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) +* Added: Basic support of offline mode for users playing on EA's Origin service (#333) * Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) * Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid!) (#362) * Fixed: Mail trucks ignoring lane arrows (#307, #338) @@ -38,6 +39,7 @@ * Meta: Added GitHub issue templates for bugs, features, translations. (#272) * Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) * Meta: Added entire `.vs/` folder to `.gitignore` (#395) +* Meta: Updated install guide to include section for EA Origin users (#333) See [Full Changelog](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/blob/master/CHANGELOG.md) for details of earlier releases. From 489f43b45cc809c8b852f52a4f6e7dea339b2033 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Fri, 28 Jun 2019 22:52:08 +0200 Subject: [PATCH 097/142] Untabify UI files --- TLM/TLM/UI/MainMenu/ClearTrafficButton.cs | 34 +- TLM/TLM/UI/MainMenu/DespawnButton.cs | 29 +- .../UI/MainMenu/JunctionRestrictionsButton.cs | 14 +- TLM/TLM/UI/MainMenu/LaneArrowsButton.cs | 14 +- TLM/TLM/UI/MainMenu/LaneConnectorButton.cs | 14 +- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 455 +++++++++--------- .../UI/MainMenu/ManualTrafficLightsButton.cs | 12 +- TLM/TLM/UI/MainMenu/MenuToolModeButton.cs | 31 +- .../UI/MainMenu/ParkingRestrictionsButton.cs | 22 +- TLM/TLM/UI/MainMenu/PrioritySignsButton.cs | 14 +- TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs | 14 +- .../UI/MainMenu/TimedTrafficLightsButton.cs | 22 +- .../UI/MainMenu/ToggleTrafficLightsButton.cs | 14 +- .../UI/MainMenu/VehicleRestrictionsButton.cs | 22 +- 14 files changed, 342 insertions(+), 369 deletions(-) diff --git a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs index 8b498ce9e..634714780 100644 --- a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs +++ b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs @@ -7,21 +7,21 @@ using TrafficManager.Manager.Impl; namespace TrafficManager.UI.MainMenu { - public class ClearTrafficButton : MenuButton { - public override bool Active => false; - public override ButtonFunction Function => ButtonFunction.ClearTraffic; - public override string Tooltip => "Clear_Traffic"; - public override bool Visible => true; + public class ClearTrafficButton : MenuButton { + public override bool Active => false; + public override ButtonFunction Function => ButtonFunction.ClearTraffic; + public override string Tooltip => "Clear_Traffic"; + public override bool Visible => true; - public override void OnClickInternal(UIMouseEventParameter p) { - ConfirmPanel.ShowModal(Translation.GetString("Clear_Traffic"), Translation.GetString("Clear_Traffic") + "?", delegate (UIComponent comp, int ret) { - if (ret == 1) { - Constants.ServiceFactory.SimulationService.AddAction(() => { - UtilityManager.Instance.ClearTraffic(); - }); - } - UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); - }); - } - } -} + public override void OnClickInternal(UIMouseEventParameter p) { + ConfirmPanel.ShowModal(Translation.GetString("Clear_Traffic"), Translation.GetString("Clear_Traffic") + "?", delegate (UIComponent comp, int ret) { + if (ret == 1) { + Constants.ServiceFactory.SimulationService.AddAction(() => { + UtilityManager.Instance.ClearTraffic(); + }); + } + UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); + }); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/DespawnButton.cs b/TLM/TLM/UI/MainMenu/DespawnButton.cs index b72f7d272..afc66b320 100644 --- a/TLM/TLM/UI/MainMenu/DespawnButton.cs +++ b/TLM/TLM/UI/MainMenu/DespawnButton.cs @@ -1,21 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework.UI; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class DespawnButton : MenuButton { - public override bool Active => false; - public override ButtonFunction Function => Options.disableDespawning ? ButtonFunction.DespawnDisabled : ButtonFunction.DespawnEnabled; - public override string Tooltip => Options.disableDespawning ? "Enable_despawning" : "Disable_despawning"; - public override bool Visible => true; + public class DespawnButton : MenuButton { + public override bool Active => false; + public override ButtonFunction Function => Options.disableDespawning ? ButtonFunction.DespawnDisabled : ButtonFunction.DespawnEnabled; + public override string Tooltip => Options.disableDespawning ? "Enable_despawning" : "Disable_despawning"; + public override bool Visible => true; - public override void OnClickInternal(UIMouseEventParameter p) { - UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); - Options.setDisableDespawning(!Options.disableDespawning); - } - } -} + public override void OnClickInternal(UIMouseEventParameter p) { + UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); + Options.setDisableDespawning(!Options.disableDespawning); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs index 29c6600ff..ea8735544 100644 --- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class JunctionRestrictionsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.JunctionRestrictions; - public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; - public override string Tooltip => "Junction_restrictions"; - public override bool Visible => Options.junctionRestrictionsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyJunctionRestrictionsTool; - } + public class JunctionRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.JunctionRestrictions; + public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; + public override string Tooltip => "Junction_restrictions"; + public override bool Visible => Options.junctionRestrictionsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyJunctionRestrictionsTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs index 88e431483..0e743c08b 100644 --- a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class LaneArrowsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.LaneChange; - public override ButtonFunction Function => ButtonFunction.LaneArrows; - public override string Tooltip => "Change_lane_arrows"; - public override bool Visible => true; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneArrowTool; - } + public class LaneArrowsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.LaneChange; + public override ButtonFunction Function => ButtonFunction.LaneArrows; + public override string Tooltip => "Change_lane_arrows"; + public override bool Visible => true; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneArrowTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs index 71a32232d..319e4428b 100644 --- a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class LaneConnectorButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.LaneConnector; - public override ButtonFunction Function => ButtonFunction.LaneConnector; - public override string Tooltip => "Lane_connector"; - public override bool Visible => Options.laneConnectorEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneConnectionsTool; - } + public class LaneConnectorButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.LaneConnector; + public override ButtonFunction Function => ButtonFunction.LaneConnector; + public override string Tooltip => "Lane_connector"; + public override bool Visible => Options.laneConnectorEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneConnectionsTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index 69b4377f5..5e31f1f65 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -16,230 +16,231 @@ using TrafficManager.Util; namespace TrafficManager.UI.MainMenu { - public class MainMenuPanel : UIPanel, IObserver { - private static readonly Type[] MENU_BUTTON_TYPES = new Type[] { - // first row - typeof(ToggleTrafficLightsButton), - typeof(ManualTrafficLightsButton), - typeof(LaneArrowsButton), - typeof(LaneConnectorButton), - typeof(DespawnButton), - typeof(ClearTrafficButton), - // second row - typeof(PrioritySignsButton), - typeof(TimedTrafficLightsButton), - typeof(JunctionRestrictionsButton), - typeof(SpeedLimitsButton), - typeof(VehicleRestrictionsButton), - typeof(ParkingRestrictionsButton), - }; - - public class SizeProfile { - public int NUM_BUTTONS_PER_ROW { get; set; } - public int NUM_ROWS { get; set; } - - public int VSPACING { get; set; } - public int HSPACING { get; set; } - public int TOP_BORDER { get; set; } - public int BUTTON_SIZE { get; set; } - - public int MENU_WIDTH { get; set; } - public int MENU_HEIGHT { get; set; } - } - - public static readonly SizeProfile[] SIZE_PROFILES = new SizeProfile[] { - new SizeProfile() { - NUM_BUTTONS_PER_ROW = 6, - NUM_ROWS = 2, - - VSPACING = 5, - HSPACING = 5, - TOP_BORDER = 25, - BUTTON_SIZE = 30, - - MENU_WIDTH = 215, - MENU_HEIGHT = 95 - }, - new SizeProfile() { - NUM_BUTTONS_PER_ROW = 6, - NUM_ROWS = 2, - - VSPACING = 5, - HSPACING = 5, - TOP_BORDER = 25, - BUTTON_SIZE = 50, - - MENU_WIDTH = 335, - MENU_HEIGHT = 135 - } - }; - - public const int DEFAULT_MENU_X = 85; - public const int DEFAULT_MENU_Y = 60; - - public MenuButton[] Buttons { get; private set; } - public UILabel VersionLabel { get; private set; } - public UILabel StatsLabel { get; private set; } - - public UIDragHandle Drag { get; private set; } - - IDisposable confDisposable; - - private SizeProfile activeProfile = null; - private bool started = false; - - //private UILabel optionsLabel; - - public override void Start() { - GlobalConfig conf = GlobalConfig.Instance; - DetermineProfile(conf); - - OnUpdate(conf); - - confDisposable = conf.Subscribe(this); - - isVisible = false; - - backgroundSprite = "GenericPanel"; - color = new Color32(64, 64, 64, 240); - - VersionLabel = AddUIComponent(); - StatsLabel = AddUIComponent(); - - Buttons = new MenuButton[MENU_BUTTON_TYPES.Length]; - for (int i = 0; i < MENU_BUTTON_TYPES.Length; ++i) { - Buttons[i] = AddUIComponent(MENU_BUTTON_TYPES[i]) as MenuButton; - } - - var dragHandler = new GameObject("TMPE_Menu_DragHandler"); - dragHandler.transform.parent = transform; - dragHandler.transform.localPosition = Vector3.zero; - Drag = dragHandler.AddComponent(); - Drag.enabled = !GlobalConfig.Instance.Main.MainMenuPosLocked; - - UpdateAllSizes(); - started = true; - } - - public override void OnDestroy() { - if (confDisposable != null) { - confDisposable.Dispose(); - } - } - - internal void SetPosLock(bool lck) { - Drag.enabled = !lck; - } - - protected override void OnPositionChanged() { - GlobalConfig config = GlobalConfig.Instance; - - bool posChanged = (config.Main.MainMenuX != (int)absolutePosition.x || config.Main.MainMenuY != (int)absolutePosition.y); - - if (posChanged) { - Log._Debug($"Menu position changed to {absolutePosition.x}|{absolutePosition.y}"); - - config.Main.MainMenuX = (int)absolutePosition.x; - config.Main.MainMenuY = (int)absolutePosition.y; - - GlobalConfig.WriteConfig(); - } - base.OnPositionChanged(); - } - - public void OnUpdate(GlobalConfig config) { - UpdatePosition(new Vector2(config.Main.MainMenuX, config.Main.MainMenuY)); - if (started) { - DetermineProfile(config); - UpdateAllSizes(); - Invalidate(); - } - } - - private void DetermineProfile(GlobalConfig conf) { - int profileIndex = conf.Main.TinyMainMenu ? 0 : 1; - activeProfile = SIZE_PROFILES[profileIndex]; - } - - public void UpdateAllSizes() { - UpdateSize(); - UpdateDragSize(); - UpdateButtons(); - } - - private void UpdateSize() { - width = activeProfile.MENU_WIDTH; - height = activeProfile.MENU_HEIGHT; - } - - private void UpdateDragSize() { - Drag.width = width; - Drag.height = activeProfile.TOP_BORDER; - } - - private void UpdateButtons() { - int i = 0; - int y = activeProfile.TOP_BORDER; - for (int row = 0; row < activeProfile.NUM_ROWS; ++row) { - int x = activeProfile.HSPACING; - for (int col = 0; col < activeProfile.NUM_BUTTONS_PER_ROW; ++col) { - if (i >= Buttons.Length) { - break; - } - - MenuButton button = Buttons[i]; - button.relativePosition = new Vector3(x, y); - button.width = activeProfile.BUTTON_SIZE; - button.height = activeProfile.BUTTON_SIZE; - button.Invalidate(); - Buttons[i++] = button; - x += activeProfile.BUTTON_SIZE + activeProfile.HSPACING; - } - y += activeProfile.BUTTON_SIZE + activeProfile.VSPACING; - } - } - - public void UpdatePosition(Vector2 pos) { - Rect rect = new Rect(pos.x, pos.y, activeProfile.MENU_WIDTH, activeProfile.MENU_HEIGHT); - Vector2 resolution = UIView.GetAView().GetScreenResolution(); - VectorUtil.ClampRectToScreen(ref rect, resolution); - Log.Info($"Setting main menu position to [{pos.x},{pos.y}]"); - absolutePosition = rect.position; - Invalidate(); - } - - public void OnGUI() { - // Return if modal window is active or the main menu is hidden - if (!this.isVisible || UIView.HasModalInput() || UIView.HasInputFocus()) { - return; - } - - // Some safety checks to not trigger while full screen/modals are open - // Check the key and then click the corresponding button - if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { - ClickToolButton(typeof(ToggleTrafficLightsButton)); - } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { - ClickToolButton(typeof(LaneArrowsButton)); - } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(LaneConnectorButton)); - } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(PrioritySignsButton)); - } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(JunctionRestrictionsButton)); - } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { - ClickToolButton(typeof(SpeedLimitsButton)); - } - } - - /// For given button class type, find it in the tool palette and send click - /// Something like typeof(ToggleTrafficLightsButton) - void ClickToolButton(Type t) { - for (var i = 0; i < MENU_BUTTON_TYPES.Length; i++) { - if (MENU_BUTTON_TYPES[i] == t) { - Buttons[i].SimulateClick(); - return; - } - } - } - } -} + public class MainMenuPanel : UIPanel, IObserver { + private static readonly Type[] MENU_BUTTON_TYPES + = { + // first row + typeof(ToggleTrafficLightsButton), + typeof(ManualTrafficLightsButton), + typeof(LaneArrowsButton), + typeof(LaneConnectorButton), + typeof(DespawnButton), + typeof(ClearTrafficButton), + // second row + typeof(PrioritySignsButton), + typeof(TimedTrafficLightsButton), + typeof(JunctionRestrictionsButton), + typeof(SpeedLimitsButton), + typeof(VehicleRestrictionsButton), + typeof(ParkingRestrictionsButton), + }; + + public class SizeProfile { + public int NUM_BUTTONS_PER_ROW { get; set; } + public int NUM_ROWS { get; set; } + + public int VSPACING { get; set; } + public int HSPACING { get; set; } + public int TOP_BORDER { get; set; } + public int BUTTON_SIZE { get; set; } + + public int MENU_WIDTH { get; set; } + public int MENU_HEIGHT { get; set; } + } + + public static readonly SizeProfile[] SIZE_PROFILES = new SizeProfile[] { + new SizeProfile() { + NUM_BUTTONS_PER_ROW = 6, + NUM_ROWS = 2, + + VSPACING = 5, + HSPACING = 5, + TOP_BORDER = 25, + BUTTON_SIZE = 30, + + MENU_WIDTH = 215, + MENU_HEIGHT = 95 + }, + new SizeProfile() { + NUM_BUTTONS_PER_ROW = 6, + NUM_ROWS = 2, + + VSPACING = 5, + HSPACING = 5, + TOP_BORDER = 25, + BUTTON_SIZE = 50, + + MENU_WIDTH = 335, + MENU_HEIGHT = 135 + } + }; + + public const int DEFAULT_MENU_X = 85; + public const int DEFAULT_MENU_Y = 60; + + public MenuButton[] Buttons { get; private set; } + public UILabel VersionLabel { get; private set; } + public UILabel StatsLabel { get; private set; } + + public UIDragHandle Drag { get; private set; } + + IDisposable confDisposable; + + private SizeProfile activeProfile = null; + private bool started = false; + + //private UILabel optionsLabel; + + public override void Start() { + GlobalConfig conf = GlobalConfig.Instance; + DetermineProfile(conf); + + OnUpdate(conf); + + confDisposable = conf.Subscribe(this); + + isVisible = false; + + backgroundSprite = "GenericPanel"; + color = new Color32(64, 64, 64, 240); + + VersionLabel = AddUIComponent(); + StatsLabel = AddUIComponent(); + + Buttons = new MenuButton[MENU_BUTTON_TYPES.Length]; + for (int i = 0; i < MENU_BUTTON_TYPES.Length; ++i) { + Buttons[i] = AddUIComponent(MENU_BUTTON_TYPES[i]) as MenuButton; + } + + var dragHandler = new GameObject("TMPE_Menu_DragHandler"); + dragHandler.transform.parent = transform; + dragHandler.transform.localPosition = Vector3.zero; + Drag = dragHandler.AddComponent(); + Drag.enabled = !GlobalConfig.Instance.Main.MainMenuPosLocked; + + UpdateAllSizes(); + started = true; + } + + public override void OnDestroy() { + if (confDisposable != null) { + confDisposable.Dispose(); + } + } + + internal void SetPosLock(bool lck) { + Drag.enabled = !lck; + } + + protected override void OnPositionChanged() { + GlobalConfig config = GlobalConfig.Instance; + + bool posChanged = (config.Main.MainMenuX != (int)absolutePosition.x || config.Main.MainMenuY != (int)absolutePosition.y); + + if (posChanged) { + Log._Debug($"Menu position changed to {absolutePosition.x}|{absolutePosition.y}"); + + config.Main.MainMenuX = (int)absolutePosition.x; + config.Main.MainMenuY = (int)absolutePosition.y; + + GlobalConfig.WriteConfig(); + } + base.OnPositionChanged(); + } + + public void OnUpdate(GlobalConfig config) { + UpdatePosition(new Vector2(config.Main.MainMenuX, config.Main.MainMenuY)); + if (started) { + DetermineProfile(config); + UpdateAllSizes(); + Invalidate(); + } + } + + private void DetermineProfile(GlobalConfig conf) { + int profileIndex = conf.Main.TinyMainMenu ? 0 : 1; + activeProfile = SIZE_PROFILES[profileIndex]; + } + + public void UpdateAllSizes() { + UpdateSize(); + UpdateDragSize(); + UpdateButtons(); + } + + private void UpdateSize() { + width = activeProfile.MENU_WIDTH; + height = activeProfile.MENU_HEIGHT; + } + + private void UpdateDragSize() { + Drag.width = width; + Drag.height = activeProfile.TOP_BORDER; + } + + private void UpdateButtons() { + int i = 0; + int y = activeProfile.TOP_BORDER; + for (int row = 0; row < activeProfile.NUM_ROWS; ++row) { + int x = activeProfile.HSPACING; + for (int col = 0; col < activeProfile.NUM_BUTTONS_PER_ROW; ++col) { + if (i >= Buttons.Length) { + break; + } + + MenuButton button = Buttons[i]; + button.relativePosition = new Vector3(x, y); + button.width = activeProfile.BUTTON_SIZE; + button.height = activeProfile.BUTTON_SIZE; + button.Invalidate(); + Buttons[i++] = button; + x += activeProfile.BUTTON_SIZE + activeProfile.HSPACING; + } + y += activeProfile.BUTTON_SIZE + activeProfile.VSPACING; + } + } + + public void UpdatePosition(Vector2 pos) { + Rect rect = new Rect(pos.x, pos.y, activeProfile.MENU_WIDTH, activeProfile.MENU_HEIGHT); + Vector2 resolution = UIView.GetAView().GetScreenResolution(); + VectorUtil.ClampRectToScreen(ref rect, resolution); + Log.Info($"Setting main menu position to [{pos.x},{pos.y}]"); + absolutePosition = rect.position; + Invalidate(); + } + + public void OnGUI() { + // Return if modal window is active or the main menu is hidden + if (!this.isVisible || UIView.HasModalInput() || UIView.HasInputFocus()) { + return; + } + + // Some safety checks to not trigger while full screen/modals are open + // Check the key and then click the corresponding button + if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { + ClickToolButton(typeof(ToggleTrafficLightsButton)); + } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneArrowsButton)); + } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneConnectorButton)); + } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(PrioritySignsButton)); + } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(JunctionRestrictionsButton)); + } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(SpeedLimitsButton)); + } + } + + /// For given button class type, find it in the tool palette and send click + /// Something like typeof(ToggleTrafficLightsButton) + void ClickToolButton(Type t) { + for (var i = 0; i < MENU_BUTTON_TYPES.Length; i++) { + if (MENU_BUTTON_TYPES[i] == t) { + Buttons[i].SimulateClick(); + return; + } + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs index 7bc01aa52..02473b712 100644 --- a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs @@ -1,10 +1,10 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class ManualTrafficLightsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.ManualSwitch; - public override ButtonFunction Function => ButtonFunction.ManualTrafficLights; - public override string Tooltip => "Manual_traffic_lights"; - public override bool Visible => Options.timedLightsEnabled; - } + public class ManualTrafficLightsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.ManualSwitch; + public override ButtonFunction Function => ButtonFunction.ManualTrafficLights; + public override string Tooltip => "Manual_traffic_lights"; + public override bool Visible => Options.timedLightsEnabled; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs b/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs index 46d6d452a..846776779 100644 --- a/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs +++ b/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs @@ -1,22 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework.UI; namespace TrafficManager.UI.MainMenu { - public abstract class MenuToolModeButton : MenuButton { - public abstract ToolMode ToolMode { get; } + public abstract class MenuToolModeButton : MenuButton { + public abstract ToolMode ToolMode { get; } - public override bool Active => ToolMode.Equals(UIBase.GetTrafficManagerTool(false)?.GetToolMode()); + public override bool Active => ToolMode.Equals(UIBase.GetTrafficManagerTool(false)?.GetToolMode()); - public override void OnClickInternal(UIMouseEventParameter p) { - if (Active) { - UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); - } else { - UIBase.GetTrafficManagerTool(true).SetToolMode(this.ToolMode); - } - } - } -} + public override void OnClickInternal(UIMouseEventParameter p) { + if (Active) { + UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); + } else { + UIBase.GetTrafficManagerTool(true).SetToolMode(this.ToolMode); + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs index b5477a663..b06b53e9a 100644 --- a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class ParkingRestrictionsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.ParkingRestrictions; - public override ButtonFunction Function => ButtonFunction.ParkingRestrictions; - public override string Tooltip => "Parking_restrictions"; - public override bool Visible => Options.parkingRestrictionsEnabled; - } -} + public class ParkingRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.ParkingRestrictions; + public override ButtonFunction Function => ButtonFunction.ParkingRestrictions; + public override string Tooltip => "Parking_restrictions"; + public override bool Visible => Options.parkingRestrictionsEnabled; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs index 9d532bc64..7e9c587e5 100644 --- a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs +++ b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class PrioritySignsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.AddPrioritySigns; - public override ButtonFunction Function => ButtonFunction.PrioritySigns; - public override string Tooltip => "Add_priority_signs"; - public override bool Visible => Options.prioritySignsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyPrioritySignsTool; - } + public class PrioritySignsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.AddPrioritySigns; + public override ButtonFunction Function => ButtonFunction.PrioritySigns; + public override string Tooltip => "Add_priority_signs"; + public override bool Visible => Options.prioritySignsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyPrioritySignsTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs index 070d41d99..f0787bff4 100644 --- a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs +++ b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class SpeedLimitsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.SpeedLimits; - public override ButtonFunction Function => ButtonFunction.SpeedLimits; - public override string Tooltip => "Speed_limits"; - public override bool Visible => Options.customSpeedLimitsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeySpeedLimitsTool; - } + public class SpeedLimitsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.SpeedLimits; + public override ButtonFunction Function => ButtonFunction.SpeedLimits; + public override string Tooltip => "Speed_limits"; + public override bool Visible => Options.customSpeedLimitsEnabled; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeySpeedLimitsTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs index d986507cc..b00126512 100644 --- a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class TimedTrafficLightsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.TimedLightsSelectNode; - public override ButtonFunction Function => ButtonFunction.TimedTrafficLights; - public override string Tooltip => "Timed_traffic_lights"; - public override bool Visible => Options.timedLightsEnabled; - } -} + public class TimedTrafficLightsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.TimedLightsSelectNode; + public override ButtonFunction Function => ButtonFunction.TimedTrafficLights; + public override string Tooltip => "Timed_traffic_lights"; + public override bool Visible => Options.timedLightsEnabled; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs index cae176cf8..c793b0005 100644 --- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs @@ -2,11 +2,11 @@ using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class ToggleTrafficLightsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.SwitchTrafficLight; - public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; - public override string Tooltip => "Switch_traffic_lights"; - public override bool Visible => true; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyToggleTrafficLightTool; - } + public class ToggleTrafficLightsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.SwitchTrafficLight; + public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; + public override string Tooltip => "Switch_traffic_lights"; + public override bool Visible => true; + public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyToggleTrafficLightTool; + } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs index 8173774d7..ad1574a90 100644 --- a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class VehicleRestrictionsButton : MenuToolModeButton { - public override ToolMode ToolMode => ToolMode.VehicleRestrictions; - public override ButtonFunction Function => ButtonFunction.VehicleRestrictions; - public override string Tooltip => "Vehicle_restrictions"; - public override bool Visible => Options.vehicleRestrictionsEnabled; - } -} + public class VehicleRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.VehicleRestrictions; + public override ButtonFunction Function => ButtonFunction.VehicleRestrictions; + public override string Tooltip => "Vehicle_restrictions"; + public override bool Visible => Options.vehicleRestrictionsEnabled; + } +} \ No newline at end of file From 365c9e693daee403a442c521d8041e35d7b4184a Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Fri, 28 Jun 2019 23:00:36 +0200 Subject: [PATCH 098/142] Untabify KeyMapping files and rename/regroup classes one per file in a directory --- TLM/TLM/State/KeyMapping.cs | 494 --- TLM/TLM/State/Keybinds/KeymappingSettings.cs | 448 +++ .../State/Keybinds/KeymappingSettingsMain.cs | 35 + TLM/TLM/State/Keybinds/TmpeRebindableKey.cs | 15 + TLM/TLM/State/Options.cs | 2699 ++++++++--------- TLM/TLM/TLM.csproj | 4 +- TLM/TLM/UI/MainMenu/ClearTrafficButton.cs | 7 +- .../UI/MainMenu/JunctionRestrictionsButton.cs | 3 +- TLM/TLM/UI/MainMenu/LaneArrowsButton.cs | 3 +- TLM/TLM/UI/MainMenu/LaneConnectorButton.cs | 3 +- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 13 +- TLM/TLM/UI/MainMenu/PrioritySignsButton.cs | 3 +- TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs | 3 +- .../UI/MainMenu/ToggleTrafficLightsButton.cs | 3 +- .../UI/MainMenu/VehicleRestrictionsButton.cs | 1 + TLM/TLM/UI/SubTools/LaneConnectorTool.cs | 5 +- TLM/TLM/UI/UIMainMenuButton.cs | 3 +- 17 files changed, 1874 insertions(+), 1868 deletions(-) delete mode 100644 TLM/TLM/State/KeyMapping.cs create mode 100644 TLM/TLM/State/Keybinds/KeymappingSettings.cs create mode 100644 TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs create mode 100644 TLM/TLM/State/Keybinds/TmpeRebindableKey.cs diff --git a/TLM/TLM/State/KeyMapping.cs b/TLM/TLM/State/KeyMapping.cs deleted file mode 100644 index 957288c1f..000000000 --- a/TLM/TLM/State/KeyMapping.cs +++ /dev/null @@ -1,494 +0,0 @@ -// Based on keymapping module from CS-MoveIt mod -// Thanks to https://github.com/Quboid/CS-MoveIt - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using ColossalFramework; -using ColossalFramework.Globalization; -using ColossalFramework.UI; -using CSUtil.Commons; -using TrafficManager.UI; -using UnityEngine; - -namespace TrafficManager.State { - /// - /// This attribute is used on key bindings and tells us where this key is used, - /// to allow using the same key in multiple occasions we need to know the category. - /// - [AttributeUsage(AttributeTargets.Field)] - public class TMPERebindableKey: Attribute { - public string Category; - public TMPERebindableKey(string cat) { - Category = cat; - } - } - - public class OptionsKeymappingMain : OptionsKeymapping { - private void Awake() { - TryCreateConfig(); - - AddReadOnlyKeymapping(Translation.GetString("Keybind_Exit_subtool"), - KeyToolCancel_ViewOnly); - - AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), - KeyToggleTMPEMainMenu, "Global"); - - AddKeymapping(Translation.GetString("Keybind_toggle_traffic_lights_tool"), - KeyToggleTrafficLightTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_lane_arrow_tool"), - KeyLaneArrowTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_lane_connections_tool"), - KeyLaneConnectionsTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_priority_signs_tool"), - KeyPrioritySignsTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_junction_restrictions_tool"), - KeyJunctionRestrictionsTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_speed_limits_tool"), - KeySpeedLimitsTool, "Global"); - - // New section: Lane Connector Tool - AddKeymapping(Translation.GetString("Keybind_lane_connector_stay_in_lane"), - KeyLaneConnectorStayInLane, "LaneConnector"); - - AddKeymapping(Translation.GetString("Keybind_lane_connector_delete"), - KeyLaneConnectorDelete, "LaneConnector"); - } - } - - public class OptionsKeymapping : UICustomControl { - protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; - private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; - - /// - /// This input key can not be changed and is not checked, instead it is display only - /// - [TMPERebindableKey("Global")] - public static SavedInputKey KeyToolCancel_ViewOnly = - new SavedInputKey("keyExitSubtool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.Escape, false, false, false), - false); - - [TMPERebindableKey("Global")] - public static SavedInputKey KeyToggleTMPEMainMenu = - new SavedInputKey("keyToggleTMPEMainMenu", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.Semicolon, false, true, false), - true); - - [TMPERebindableKey("Global")] - public static SavedInputKey KeyToggleTrafficLightTool = - new SavedInputKey("keyToggleTrafficLightTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TMPERebindableKey("Global")] - public static SavedInputKey KeyLaneArrowTool = - new SavedInputKey("keyLaneArrowTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TMPERebindableKey("Global")] - public static SavedInputKey KeyLaneConnectionsTool = - new SavedInputKey("keyLaneConnectionsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TMPERebindableKey("Global")] - public static SavedInputKey KeyPrioritySignsTool = - new SavedInputKey("keyPrioritySignsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TMPERebindableKey("Global")] - public static SavedInputKey KeyJunctionRestrictionsTool = - new SavedInputKey("keyJunctionRestrictionsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TMPERebindableKey("Global")] - public static SavedInputKey KeySpeedLimitsTool = - new SavedInputKey("keySpeedLimitsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TMPERebindableKey("LaneConnector")] - public static SavedInputKey KeyLaneConnectorStayInLane = - new SavedInputKey("keyLaneConnectorStayInLane", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.S, false, true, false), - true); - - [TMPERebindableKey("LaneConnector")] - public static SavedInputKey KeyLaneConnectorDelete = - new SavedInputKey("keyLaneConnectorDelete", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.Delete, false, false, false), - true); - - private SavedInputKey editingBinding_; - private string editingBindingCategory_; - - private int count_; - - protected void TryCreateConfig() { - try { - // Creating setting file - if (GameSettings.FindSettingsFileByName(KEYBOARD_SHORTCUTS_FILENAME) == null) { - GameSettings.AddSettingsFile(new SettingsFile - {fileName = KEYBOARD_SHORTCUTS_FILENAME}); - } - } - catch (Exception) { - Log._Debug("Could not load/create the keyboard shortcuts file."); - } - } - - /// - /// Creates a row in the current panel with the label and the button which will prompt user to press a new key. - /// - /// Text to display - /// A SavedInputKey from GlobalConfig.KeyboardShortcuts - protected void AddKeymapping(string label, SavedInputKey savedInputKey, string category) { - var uiPanel = component.AttachUIComponent( - UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; - if (count_++ % 2 == 1) { - uiPanel.backgroundSprite = null; - } - - // Create a label - var uiLabel = uiPanel.Find("Name"); - - // Create a button which displays the shortcut and modifies it on click - var uiButton = uiPanel.Find("Binding"); - uiButton.eventKeyDown += OnBindingKeyDown; - uiButton.eventMouseDown += OnBindingMouseDown; - uiButton.text = savedInputKey.ToLocalizedString("KEYNAME"); - uiButton.objectUserData = savedInputKey; - uiButton.stringUserData = category; - - // Set label text (as provided) and set button text from the SavedInputKey - uiLabel.text = label; - } - - /// - /// Creates a line of key mapping but does not allow changing it. - /// Used to improve awareness. - /// - /// - /// - protected void AddReadOnlyKeymapping(string label, SavedInputKey inputKey) { - var uiPanel = component.AttachUIComponent( - UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; - if (count_++ % 2 == 1) { - uiPanel.backgroundSprite = null; - } - - // Create a label - var uiLabel = uiPanel.Find("Name"); - - // Create a button which displays the shortcut and modifies it on click - var uiReadOnlyKey = uiPanel.Find("Binding"); - uiReadOnlyKey.Disable(); - - // Set label text (as provided) and set button text from the InputKey - uiLabel.text = label; - uiReadOnlyKey.text = inputKey.ToLocalizedString("KEYNAME"); - uiReadOnlyKey.objectUserData = inputKey; - } - - protected void OnEnable() { - LocaleManager.eventLocaleChanged += OnLocaleChanged; - } - - protected void OnDisable() { - LocaleManager.eventLocaleChanged -= OnLocaleChanged; - } - - protected void OnLocaleChanged() { - RefreshBindableInputs(); - } - - protected bool IsModifierKey(KeyCode code) { - return code == KeyCode.LeftControl || code == KeyCode.RightControl || - code == KeyCode.LeftShift || code == KeyCode.RightShift || code == KeyCode.LeftAlt || - code == KeyCode.RightAlt; - } - - protected bool IsControlDown() { - return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); - } - - protected bool IsShiftDown() { - return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - } - - protected bool IsAltDown() { - return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); - } - - protected bool IsUnbindableMouseButton(UIMouseButton code) { - return code == UIMouseButton.Left || code == UIMouseButton.Right; - } - - protected KeyCode ButtonToKeycode(UIMouseButton button) { - if (button == UIMouseButton.Left) { - return KeyCode.Mouse0; - } - - if (button == UIMouseButton.Right) { - return KeyCode.Mouse1; - } - - if (button == UIMouseButton.Middle) { - return KeyCode.Mouse2; - } - - if (button == UIMouseButton.Special0) { - return KeyCode.Mouse3; - } - - if (button == UIMouseButton.Special1) { - return KeyCode.Mouse4; - } - - if (button == UIMouseButton.Special2) { - return KeyCode.Mouse5; - } - - if (button == UIMouseButton.Special3) { - return KeyCode.Mouse6; - } - - return KeyCode.None; - } - - protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { - // This will only work if the user clicked the modify button - // otherwise no effect - if (editingBinding_ != null && !IsModifierKey(p.keycode)) { - p.Use(); - UIView.PopModal(); - var keycode = p.keycode; - var inputKey = (p.keycode == KeyCode.Escape) - ? editingBinding_.value - : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); - var category = p.source.stringUserData; - - if (p.keycode == KeyCode.Backspace) { - inputKey = SavedInputKey.Empty; - } - var maybeConflict = FindConflict(inputKey, category); - if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); - } else { - editingBinding_.value = inputKey; - } - - var uITextComponent = p.source as UITextComponent; - uITextComponent.text = editingBinding_.ToLocalizedString("KEYNAME"); - editingBinding_ = null; - editingBindingCategory_ = string.Empty; - } - } - - protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { - // This will only work if the user is not in the process of changing the shortcut - if (editingBinding_ == null) { - p.Use(); - editingBinding_ = (SavedInputKey) p.source.objectUserData; - editingBindingCategory_ = p.source.stringUserData; - var uIButton = p.source as UIButton; - uIButton.buttonsMask = - UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | - UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | - UIMouseButton.Special3; - uIButton.text = "Press any key"; - p.source.Focus(); - UIView.PushModal(p.source); - } else if (!IsUnbindableMouseButton(p.buttons)) { - // This will work if the user clicks while the shortcut change is in progress - p.Use(); - UIView.PopModal(); - var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), - IsControlDown(), IsShiftDown(), - IsAltDown()); - var category = p.source.stringUserData; - var maybeConflict = FindConflict(inputKey, category); - if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); - } else { - editingBinding_.value = inputKey; - } - - var uIButton2 = p.source as UIButton; - uIButton2.text = editingBinding_.ToLocalizedString("KEYNAME"); - uIButton2.buttonsMask = UIMouseButton.Left; - editingBinding_ = null; - editingBindingCategory_ = string.Empty; - } - } - - protected void RefreshBindableInputs() { - foreach (var current in component.GetComponentsInChildren()) { - var uITextComponent = current.Find("Binding"); - if (uITextComponent != null) { - var savedInputKey = uITextComponent.objectUserData as SavedInputKey; - if (savedInputKey != null) { - uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); - } - } - - var uILabel = current.Find("Name"); - if (uILabel != null) { - uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); - } - } - } - - protected void RefreshKeyMapping() { - foreach (var current in component.GetComponentsInChildren()) { - var uITextComponent = current.Find("Binding"); - var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; - if (editingBinding_ != savedInputKey) { - uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); - } - } - } - - /// - /// For an inputkey, try find where possibly it is already used. - /// This covers game Settings class, and self (OptionsKeymapping class). - /// - /// Key to search for the conflicts - /// - private string FindConflict(InputKey sample, string sampleCategory) { - if (sample == SavedInputKey.Empty - || sample == SavedInputKey.Encode(KeyCode.None, false, false, false)) { - // empty key never conflicts - return string.Empty; - } - - var inGameSettings = FindConflictInGameSettings(sample); - if (!string.IsNullOrEmpty(inGameSettings)) { - return inGameSettings; - } - - // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. - var saveEditingBinding = editingBinding_; - editingBinding_.value = SavedInputKey.Empty; - - // Check in TMPE settings - var tmpeSettingsType = typeof(OptionsKeymapping); - var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); - - var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); - editingBinding_ = saveEditingBinding; - return inTmpe; - } - - private static string FindConflictInGameSettings(InputKey sample) { - var fieldList = typeof(Settings).GetFields(BindingFlags.Static | BindingFlags.Public); - foreach (var field in fieldList) { - var customAttributes = field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; - if (customAttributes != null && customAttributes.Length > 0) { - var category = customAttributes[0].category; - if (category != string.Empty && category != "Game") { - // Ignore other categories: MapEditor, Decoration, ThemeEditor, ScenarioEditor - continue; - } - - var str = field.GetValue(null) as string; - - var savedInputKey = new SavedInputKey(str, - Settings.gameSettingsFile, - GetDefaultEntryInGameSettings(str), - true); - if (savedInputKey.value == sample) { - return (category == string.Empty ? string.Empty : (category + " -- ")) - + CamelCaseSplit(field.Name); - } - } - } - - return string.Empty; - } - - private static InputKey GetDefaultEntryInGameSettings(string entryName) { - var field = typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); - if (field == null) { - return 0; - } - var obj = field.GetValue(null); - if (obj is InputKey) { - return (InputKey)obj; - } - return 0; - } - - /// - /// For given key and category check TM:PE settings for the Global category - /// and the same category if it is not Global. This will allow reusing key in other tool - /// categories without conflicting. - /// - /// The key to search for - /// The category Global or some tool name - /// Fields of the key settings class - /// Empty string if no conflicts otherwise the key name to print an error - private static string FindConflictInTmpe(InputKey sample, string sampleCategory, FieldInfo[] fields) { - foreach (var field in fields) { - // This will match inputkeys of TMPE key settings - if (field.FieldType != typeof(SavedInputKey)) { - continue; - } - - var rebindableKeyAttrs = field.GetCustomAttributes( - typeof(TMPERebindableKey), - false) as TMPERebindableKey[]; - if (rebindableKeyAttrs == null || rebindableKeyAttrs.Length <= 0) { - continue; - } - - // Check category, category=Global will check keys in all categories - // category= will check Global and its own only - var rebindableKeyCategory = rebindableKeyAttrs[0].Category; - if (sampleCategory != "Global" && sampleCategory != rebindableKeyCategory) { - continue; - } - - var key = (SavedInputKey) field.GetValue(null); - if (key.value == sample) { - return "TM:PE, " - + Translation.GetString("Keybind_category_" + rebindableKeyCategory) - + " -- " + CamelCaseSplit(field.Name); - } - } - - return string.Empty; - } - - private static string CamelCaseSplit(string s) { - var words = Regex.Matches(s, @"([A-Z][a-z]+)") - .Cast() - .Select(m => m.Value); - - return string.Join(" ", words.ToArray()); - } - } -} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeymappingSettings.cs b/TLM/TLM/State/Keybinds/KeymappingSettings.cs new file mode 100644 index 000000000..42e1f4d0a --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeymappingSettings.cs @@ -0,0 +1,448 @@ +// Based on keymapping module from CS-MoveIt mod +// Thanks to https://github.com/Quboid/CS-MoveIt + +using System; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using ColossalFramework; +using ColossalFramework.Globalization; +using ColossalFramework.UI; +using CSUtil.Commons; +using TrafficManager.UI; +using UnityEngine; + +namespace TrafficManager.State.Keybinds { + public class KeymappingSettings : UICustomControl { + protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; + private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; + + /// + /// This input key can not be changed and is not checked, instead it is display only + /// + [TmpeRebindableKey("Global")] + public static SavedInputKey KeyToolCancel_ViewOnly = + new SavedInputKey("keyExitSubtool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.Escape, false, false, false), + false); + + [TmpeRebindableKey("Global")] + public static SavedInputKey KeyToggleTMPEMainMenu = + new SavedInputKey("keyToggleTMPEMainMenu", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.Semicolon, false, true, false), + true); + + [TmpeRebindableKey("Global")] + public static SavedInputKey KeyToggleTrafficLightTool = + new SavedInputKey("keyToggleTrafficLightTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Empty, + true); + + [TmpeRebindableKey("Global")] + public static SavedInputKey KeyLaneArrowTool = + new SavedInputKey("keyLaneArrowTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Empty, + true); + + [TmpeRebindableKey("Global")] + public static SavedInputKey KeyLaneConnectionsTool = + new SavedInputKey("keyLaneConnectionsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Empty, + true); + + [TmpeRebindableKey("Global")] + public static SavedInputKey KeyPrioritySignsTool = + new SavedInputKey("keyPrioritySignsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Empty, + true); + + [TmpeRebindableKey("Global")] + public static SavedInputKey KeyJunctionRestrictionsTool = + new SavedInputKey("keyJunctionRestrictionsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Empty, + true); + + [TmpeRebindableKey("Global")] + public static SavedInputKey KeySpeedLimitsTool = + new SavedInputKey("keySpeedLimitsTool", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Empty, + true); + + [TmpeRebindableKey("LaneConnector")] + public static SavedInputKey KeyLaneConnectorStayInLane = + new SavedInputKey("keyLaneConnectorStayInLane", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.S, false, true, false), + true); + + [TmpeRebindableKey("LaneConnector")] + public static SavedInputKey KeyLaneConnectorDelete = + new SavedInputKey("keyLaneConnectorDelete", + KEYBOARD_SHORTCUTS_FILENAME, + SavedInputKey.Encode(KeyCode.Delete, false, false, false), + true); + + private SavedInputKey editingBinding_; + private string editingBindingCategory_; + + private int count_; + + protected void TryCreateConfig() { + try { + // Creating setting file + if (GameSettings.FindSettingsFileByName(KEYBOARD_SHORTCUTS_FILENAME) == null) { + GameSettings.AddSettingsFile(new SettingsFile + {fileName = KEYBOARD_SHORTCUTS_FILENAME}); + } + } + catch (Exception) { + Log._Debug("Could not load/create the keyboard shortcuts file."); + } + } + + /// + /// Creates a row in the current panel with the label and the button which will prompt user to press a new key. + /// + /// Text to display + /// A SavedInputKey from GlobalConfig.KeyboardShortcuts + protected void AddKeymapping(string label, SavedInputKey savedInputKey, string category) { + var uiPanel = component.AttachUIComponent( + UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; + if (count_++ % 2 == 1) { + uiPanel.backgroundSprite = null; + } + + // Create a label + var uiLabel = uiPanel.Find("Name"); + + // Create a button which displays the shortcut and modifies it on click + var uiButton = uiPanel.Find("Binding"); + uiButton.eventKeyDown += OnBindingKeyDown; + uiButton.eventMouseDown += OnBindingMouseDown; + uiButton.text = savedInputKey.ToLocalizedString("KEYNAME"); + uiButton.objectUserData = savedInputKey; + uiButton.stringUserData = category; + + // Set label text (as provided) and set button text from the SavedInputKey + uiLabel.text = label; + } + + /// + /// Creates a line of key mapping but does not allow changing it. + /// Used to improve awareness. + /// + /// + /// + protected void AddReadOnlyKeymapping(string label, SavedInputKey inputKey) { + var uiPanel = component.AttachUIComponent( + UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; + if (count_++ % 2 == 1) { + uiPanel.backgroundSprite = null; + } + + // Create a label + var uiLabel = uiPanel.Find("Name"); + + // Create a button which displays the shortcut and modifies it on click + var uiReadOnlyKey = uiPanel.Find("Binding"); + uiReadOnlyKey.Disable(); + + // Set label text (as provided) and set button text from the InputKey + uiLabel.text = label; + uiReadOnlyKey.text = inputKey.ToLocalizedString("KEYNAME"); + uiReadOnlyKey.objectUserData = inputKey; + } + + protected void OnEnable() { + LocaleManager.eventLocaleChanged += OnLocaleChanged; + } + + protected void OnDisable() { + LocaleManager.eventLocaleChanged -= OnLocaleChanged; + } + + protected void OnLocaleChanged() { + RefreshBindableInputs(); + } + + protected bool IsModifierKey(KeyCode code) { + return code == KeyCode.LeftControl || code == KeyCode.RightControl || + code == KeyCode.LeftShift || code == KeyCode.RightShift || code == KeyCode.LeftAlt || + code == KeyCode.RightAlt; + } + + protected bool IsControlDown() { + return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + } + + protected bool IsShiftDown() { + return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + } + + protected bool IsAltDown() { + return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); + } + + protected bool IsUnbindableMouseButton(UIMouseButton code) { + return code == UIMouseButton.Left || code == UIMouseButton.Right; + } + + protected KeyCode ButtonToKeycode(UIMouseButton button) { + if (button == UIMouseButton.Left) { + return KeyCode.Mouse0; + } + + if (button == UIMouseButton.Right) { + return KeyCode.Mouse1; + } + + if (button == UIMouseButton.Middle) { + return KeyCode.Mouse2; + } + + if (button == UIMouseButton.Special0) { + return KeyCode.Mouse3; + } + + if (button == UIMouseButton.Special1) { + return KeyCode.Mouse4; + } + + if (button == UIMouseButton.Special2) { + return KeyCode.Mouse5; + } + + if (button == UIMouseButton.Special3) { + return KeyCode.Mouse6; + } + + return KeyCode.None; + } + + protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { + // This will only work if the user clicked the modify button + // otherwise no effect + if (editingBinding_ != null && !IsModifierKey(p.keycode)) { + p.Use(); + UIView.PopModal(); + var keycode = p.keycode; + var inputKey = (p.keycode == KeyCode.Escape) + ? editingBinding_.value + : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); + var category = p.source.stringUserData; + + if (p.keycode == KeyCode.Backspace) { + inputKey = SavedInputKey.Empty; + } + var maybeConflict = FindConflict(inputKey, category); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, + false); + } else { + editingBinding_.value = inputKey; + } + + var uITextComponent = p.source as UITextComponent; + uITextComponent.text = editingBinding_.ToLocalizedString("KEYNAME"); + editingBinding_ = null; + editingBindingCategory_ = string.Empty; + } + } + + protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { + // This will only work if the user is not in the process of changing the shortcut + if (editingBinding_ == null) { + p.Use(); + editingBinding_ = (SavedInputKey) p.source.objectUserData; + editingBindingCategory_ = p.source.stringUserData; + var uIButton = p.source as UIButton; + uIButton.buttonsMask = + UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | + UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | + UIMouseButton.Special3; + uIButton.text = "Press any key"; + p.source.Focus(); + UIView.PushModal(p.source); + } else if (!IsUnbindableMouseButton(p.buttons)) { + // This will work if the user clicks while the shortcut change is in progress + p.Use(); + UIView.PopModal(); + var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), + IsControlDown(), IsShiftDown(), + IsAltDown()); + var category = p.source.stringUserData; + var maybeConflict = FindConflict(inputKey, category); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, + false); + } else { + editingBinding_.value = inputKey; + } + + var uIButton2 = p.source as UIButton; + uIButton2.text = editingBinding_.ToLocalizedString("KEYNAME"); + uIButton2.buttonsMask = UIMouseButton.Left; + editingBinding_ = null; + editingBindingCategory_ = string.Empty; + } + } + + protected void RefreshBindableInputs() { + foreach (var current in component.GetComponentsInChildren()) { + var uITextComponent = current.Find("Binding"); + if (uITextComponent != null) { + var savedInputKey = uITextComponent.objectUserData as SavedInputKey; + if (savedInputKey != null) { + uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + } + } + + var uILabel = current.Find("Name"); + if (uILabel != null) { + uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); + } + } + } + + protected void RefreshKeyMapping() { + foreach (var current in component.GetComponentsInChildren()) { + var uITextComponent = current.Find("Binding"); + var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; + if (editingBinding_ != savedInputKey) { + uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + } + } + } + + /// + /// For an inputkey, try find where possibly it is already used. + /// This covers game Settings class, and self (OptionsKeymapping class). + /// + /// Key to search for the conflicts + /// + private string FindConflict(InputKey sample, string sampleCategory) { + if (sample == SavedInputKey.Empty + || sample == SavedInputKey.Encode(KeyCode.None, false, false, false)) { + // empty key never conflicts + return string.Empty; + } + + var inGameSettings = FindConflictInGameSettings(sample); + if (!string.IsNullOrEmpty(inGameSettings)) { + return inGameSettings; + } + + // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. + var saveEditingBinding = editingBinding_; + editingBinding_.value = SavedInputKey.Empty; + + // Check in TMPE settings + var tmpeSettingsType = typeof(KeymappingSettings); + var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); + + var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); + editingBinding_ = saveEditingBinding; + return inTmpe; + } + + private static string FindConflictInGameSettings(InputKey sample) { + var fieldList = typeof(Settings).GetFields(BindingFlags.Static | BindingFlags.Public); + foreach (var field in fieldList) { + var customAttributes = field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; + if (customAttributes != null && customAttributes.Length > 0) { + var category = customAttributes[0].category; + if (category != string.Empty && category != "Game") { + // Ignore other categories: MapEditor, Decoration, ThemeEditor, ScenarioEditor + continue; + } + + var str = field.GetValue(null) as string; + + var savedInputKey = new SavedInputKey(str, + Settings.gameSettingsFile, + GetDefaultEntryInGameSettings(str), + true); + if (savedInputKey.value == sample) { + return (category == string.Empty ? string.Empty : (category + " -- ")) + + CamelCaseSplit(field.Name); + } + } + } + + return string.Empty; + } + + private static InputKey GetDefaultEntryInGameSettings(string entryName) { + var field = typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); + if (field == null) { + return 0; + } + var obj = field.GetValue(null); + if (obj is InputKey) { + return (InputKey)obj; + } + return 0; + } + + /// + /// For given key and category check TM:PE settings for the Global category + /// and the same category if it is not Global. This will allow reusing key in other tool + /// categories without conflicting. + /// + /// The key to search for + /// The category Global or some tool name + /// Fields of the key settings class + /// Empty string if no conflicts otherwise the key name to print an error + private static string FindConflictInTmpe(InputKey sample, string sampleCategory, FieldInfo[] fields) { + foreach (var field in fields) { + // This will match inputkeys of TMPE key settings + if (field.FieldType != typeof(SavedInputKey)) { + continue; + } + + var rebindableKeyAttrs = field.GetCustomAttributes( + typeof(TmpeRebindableKey), + false) as TmpeRebindableKey[]; + if (rebindableKeyAttrs == null || rebindableKeyAttrs.Length <= 0) { + continue; + } + + // Check category, category=Global will check keys in all categories + // category= will check Global and its own only + var rebindableKeyCategory = rebindableKeyAttrs[0].Category; + if (sampleCategory != "Global" && sampleCategory != rebindableKeyCategory) { + continue; + } + + var key = (SavedInputKey) field.GetValue(null); + if (key.value == sample) { + return "TM:PE, " + + Translation.GetString("Keybind_category_" + rebindableKeyCategory) + + " -- " + CamelCaseSplit(field.Name); + } + } + + return string.Empty; + } + + private static string CamelCaseSplit(string s) { + var words = Regex.Matches(s, @"([A-Z][a-z]+)") + .Cast() + .Select(m => m.Value); + + return string.Join(" ", words.ToArray()); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs b/TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs new file mode 100644 index 000000000..80764b533 --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs @@ -0,0 +1,35 @@ +using TrafficManager.UI; + +namespace TrafficManager.State.Keybinds { + public class KeymappingSettingsMain : KeymappingSettings { + private void Awake() { + TryCreateConfig(); + + AddReadOnlyKeymapping(Translation.GetString("Keybind_Exit_subtool"), + KeyToolCancel_ViewOnly); + + AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), + KeyToggleTMPEMainMenu, "Global"); + + AddKeymapping(Translation.GetString("Keybind_toggle_traffic_lights_tool"), + KeyToggleTrafficLightTool, "Global"); + AddKeymapping(Translation.GetString("Keybind_use_lane_arrow_tool"), + KeyLaneArrowTool, "Global"); + AddKeymapping(Translation.GetString("Keybind_use_lane_connections_tool"), + KeyLaneConnectionsTool, "Global"); + AddKeymapping(Translation.GetString("Keybind_use_priority_signs_tool"), + KeyPrioritySignsTool, "Global"); + AddKeymapping(Translation.GetString("Keybind_use_junction_restrictions_tool"), + KeyJunctionRestrictionsTool, "Global"); + AddKeymapping(Translation.GetString("Keybind_use_speed_limits_tool"), + KeySpeedLimitsTool, "Global"); + + // New section: Lane Connector Tool + AddKeymapping(Translation.GetString("Keybind_lane_connector_stay_in_lane"), + KeyLaneConnectorStayInLane, "LaneConnector"); + + AddKeymapping(Translation.GetString("Keybind_lane_connector_delete"), + KeyLaneConnectorDelete, "LaneConnector"); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/TmpeRebindableKey.cs b/TLM/TLM/State/Keybinds/TmpeRebindableKey.cs new file mode 100644 index 000000000..54ab42dab --- /dev/null +++ b/TLM/TLM/State/Keybinds/TmpeRebindableKey.cs @@ -0,0 +1,15 @@ +using System; + +namespace TrafficManager.State.Keybinds { + /// + /// This attribute is used on key bindings and tells us where this key is used, + /// to allow using the same key in multiple occasions we need to know the category. + /// + [AttributeUsage(AttributeTargets.Field)] + public class TmpeRebindableKey: Attribute { + public string Category; + public TmpeRebindableKey(string cat) { + Category = cat; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index 02424a471..fca58d05c 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -1,534 +1,529 @@ using System; using System.Collections.Generic; -using System.Text; using UnityEngine; -using ColossalFramework; using ColossalFramework.UI; using ICities; -using TrafficManager.Geometry; -using TrafficManager.State; using TrafficManager.UI; -using ColossalFramework.Plugins; -using ColossalFramework.Globalization; using TrafficManager.Manager; using CSUtil.Commons; using System.Reflection; using TrafficManager.Manager.Impl; +using TrafficManager.State.Keybinds; using TrafficManager.Traffic.Data; namespace TrafficManager.State { - public class Options : MonoBehaviour { - private static UIDropDown languageDropdown = null; - private static UIDropDown simAccuracyDropdown = null; - //private static UIDropDown laneChangingRandomizationDropdown = null; - private static UICheckBox instantEffectsToggle = null; - private static UICheckBox lockButtonToggle = null; - private static UICheckBox lockMenuToggle = null; - private static UISlider guiTransparencySlider = null; - private static UISlider overlayTransparencySlider = null; - private static UICheckBox tinyMenuToggle = null; - private static UICheckBox enableTutorialToggle = null; - private static UICheckBox showCompatibilityCheckErrorToggle = null; - private static UICheckBox scanForKnownIncompatibleModsToggle = null; - private static UICheckBox ignoreDisabledModsToggle = null; - private static UICheckBox displayMphToggle = null; - private static UIDropDown roadSignsMphThemeDropdown = null; - private static UICheckBox individualDrivingStyleToggle = null; - private static UIDropDown recklessDriversDropdown = null; - private static UICheckBox relaxedBussesToggle = null; - private static UICheckBox allRelaxedToggle = null; - private static UICheckBox evacBussesMayIgnoreRulesToggle = null; - private static UICheckBox prioritySignsOverlayToggle = null; - private static UICheckBox timedLightsOverlayToggle = null; - private static UICheckBox speedLimitsOverlayToggle = null; - private static UICheckBox vehicleRestrictionsOverlayToggle = null; - private static UICheckBox parkingRestrictionsOverlayToggle = null; - private static UICheckBox junctionRestrictionsOverlayToggle = null; - private static UICheckBox connectedLanesOverlayToggle = null; - private static UICheckBox nodesOverlayToggle = null; - private static UICheckBox vehicleOverlayToggle = null; + public class Options : MonoBehaviour { + private static UIDropDown languageDropdown = null; + private static UIDropDown simAccuracyDropdown = null; + //private static UIDropDown laneChangingRandomizationDropdown = null; + private static UICheckBox instantEffectsToggle = null; + private static UICheckBox lockButtonToggle = null; + private static UICheckBox lockMenuToggle = null; + private static UISlider guiTransparencySlider = null; + private static UISlider overlayTransparencySlider = null; + private static UICheckBox tinyMenuToggle = null; + private static UICheckBox enableTutorialToggle = null; + private static UICheckBox showCompatibilityCheckErrorToggle = null; + private static UICheckBox scanForKnownIncompatibleModsToggle = null; + private static UICheckBox ignoreDisabledModsToggle = null; + private static UICheckBox displayMphToggle = null; + private static UIDropDown roadSignsMphThemeDropdown = null; + private static UICheckBox individualDrivingStyleToggle = null; + private static UIDropDown recklessDriversDropdown = null; + private static UICheckBox relaxedBussesToggle = null; + private static UICheckBox allRelaxedToggle = null; + private static UICheckBox evacBussesMayIgnoreRulesToggle = null; + private static UICheckBox prioritySignsOverlayToggle = null; + private static UICheckBox timedLightsOverlayToggle = null; + private static UICheckBox speedLimitsOverlayToggle = null; + private static UICheckBox vehicleRestrictionsOverlayToggle = null; + private static UICheckBox parkingRestrictionsOverlayToggle = null; + private static UICheckBox junctionRestrictionsOverlayToggle = null; + private static UICheckBox connectedLanesOverlayToggle = null; + private static UICheckBox nodesOverlayToggle = null; + private static UICheckBox vehicleOverlayToggle = null; #if DEBUG - private static UICheckBox citizenOverlayToggle = null; - private static UICheckBox buildingOverlayToggle = null; + private static UICheckBox citizenOverlayToggle = null; + private static UICheckBox buildingOverlayToggle = null; #endif - private static UICheckBox allowEnterBlockedJunctionsToggle = null; - private static UICheckBox allowUTurnsToggle = null; - private static UICheckBox allowNearTurnOnRedToggle = null; - private static UICheckBox allowFarTurnOnRedToggle = null; - private static UICheckBox allowLaneChangesWhileGoingStraightToggle = null; - private static UICheckBox trafficLightPriorityRulesToggle = null; - private static UIDropDown vehicleRestrictionsAggressionDropdown = null; - private static UICheckBox banRegularTrafficOnBusLanesToggle = null; - private static UICheckBox disableDespawningToggle = null; - - private static UICheckBox strongerRoadConditionEffectsToggle = null; - private static UICheckBox prohibitPocketCarsToggle = null; - private static UICheckBox advancedAIToggle = null; - private static UICheckBox realisticPublicTransportToggle = null; - private static UISlider altLaneSelectionRatioSlider = null; - private static UICheckBox highwayRulesToggle = null; - private static UICheckBox preferOuterLaneToggle = null; - private static UICheckBox showLanesToggle = null; + private static UICheckBox allowEnterBlockedJunctionsToggle = null; + private static UICheckBox allowUTurnsToggle = null; + private static UICheckBox allowNearTurnOnRedToggle = null; + private static UICheckBox allowFarTurnOnRedToggle = null; + private static UICheckBox allowLaneChangesWhileGoingStraightToggle = null; + private static UICheckBox trafficLightPriorityRulesToggle = null; + private static UIDropDown vehicleRestrictionsAggressionDropdown = null; + private static UICheckBox banRegularTrafficOnBusLanesToggle = null; + private static UICheckBox disableDespawningToggle = null; + + private static UICheckBox strongerRoadConditionEffectsToggle = null; + private static UICheckBox prohibitPocketCarsToggle = null; + private static UICheckBox advancedAIToggle = null; + private static UICheckBox realisticPublicTransportToggle = null; + private static UISlider altLaneSelectionRatioSlider = null; + private static UICheckBox highwayRulesToggle = null; + private static UICheckBox preferOuterLaneToggle = null; + private static UICheckBox showLanesToggle = null; #if QUEUEDSTATS - private static UICheckBox showPathFindStatsToggle = null; + private static UICheckBox showPathFindStatsToggle = null; #endif - private static UIButton resetStuckEntitiesBtn = null; - - private static UICheckBox enablePrioritySignsToggle = null; - private static UICheckBox enableTimedLightsToggle = null; - private static UICheckBox enableCustomSpeedLimitsToggle = null; - private static UICheckBox enableVehicleRestrictionsToggle = null; - private static UICheckBox enableParkingRestrictionsToggle = null; - private static UICheckBox enableJunctionRestrictionsToggle = null; - private static UICheckBox turnOnRedEnabledToggle = null; - private static UICheckBox enableLaneConnectorToggle = null; - - private static UIButton removeParkedVehiclesBtn = null; + private static UIButton resetStuckEntitiesBtn = null; + + private static UICheckBox enablePrioritySignsToggle = null; + private static UICheckBox enableTimedLightsToggle = null; + private static UICheckBox enableCustomSpeedLimitsToggle = null; + private static UICheckBox enableVehicleRestrictionsToggle = null; + private static UICheckBox enableParkingRestrictionsToggle = null; + private static UICheckBox enableJunctionRestrictionsToggle = null; + private static UICheckBox turnOnRedEnabledToggle = null; + private static UICheckBox enableLaneConnectorToggle = null; + + private static UIButton removeParkedVehiclesBtn = null; #if DEBUG - private static UIButton resetSpeedLimitsBtn = null; - private static List debugSwitchFields = new List(); - private static List debugValueFields = new List(); - private static UITextField pathCostMultiplicatorField = null; - private static UITextField pathCostMultiplicator2Field = null; + private static UIButton resetSpeedLimitsBtn = null; + private static List debugSwitchFields = new List(); + private static List debugValueFields = new List(); + private static UITextField pathCostMultiplicatorField = null; + private static UITextField pathCostMultiplicator2Field = null; #endif - private static UIButton reloadGlobalConfBtn = null; - private static UIButton resetGlobalConfBtn = null; - - public static int roadSignMphStyleInt; - public static bool instantEffects = true; - public static int simAccuracy = 0; - //public static int laneChangingRandomization = 2; - public static bool individualDrivingStyle = true; - public static int recklessDrivers = 3; - public static bool relaxedBusses = false; - public static bool allRelaxed = false; - public static bool evacBussesMayIgnoreRules = false; - public static bool prioritySignsOverlay = false; - public static bool timedLightsOverlay = false; - public static bool speedLimitsOverlay = false; - public static bool vehicleRestrictionsOverlay = false; - public static bool parkingRestrictionsOverlay = false; - public static bool junctionRestrictionsOverlay = false; - public static bool connectedLanesOverlay = false; + private static UIButton reloadGlobalConfBtn = null; + private static UIButton resetGlobalConfBtn = null; + + public static int roadSignMphStyleInt; + public static bool instantEffects = true; + public static int simAccuracy = 0; + //public static int laneChangingRandomization = 2; + public static bool individualDrivingStyle = true; + public static int recklessDrivers = 3; + public static bool relaxedBusses = false; + public static bool allRelaxed = false; + public static bool evacBussesMayIgnoreRules = false; + public static bool prioritySignsOverlay = false; + public static bool timedLightsOverlay = false; + public static bool speedLimitsOverlay = false; + public static bool vehicleRestrictionsOverlay = false; + public static bool parkingRestrictionsOverlay = false; + public static bool junctionRestrictionsOverlay = false; + public static bool connectedLanesOverlay = false; #if QUEUEDSTATS - public static bool showPathFindStats = + public static bool showPathFindStats = #if DEBUG - true; + true; #else false; #endif #endif #if DEBUG - public static bool nodesOverlay = false; - public static bool vehicleOverlay = false; - public static bool citizenOverlay = false; - public static bool buildingOverlay = false; + public static bool nodesOverlay = false; + public static bool vehicleOverlay = false; + public static bool citizenOverlay = false; + public static bool buildingOverlay = false; #else public static bool nodesOverlay = false; public static bool vehicleOverlay = false; public static bool citizenOverlay = false; public static bool buildingOverlay = false; #endif - public static bool allowEnterBlockedJunctions = false; - public static bool allowUTurns = false; - public static bool allowNearTurnOnRed = false; - public static bool allowFarTurnOnRed = false; - public static bool allowLaneChangesWhileGoingStraight = false; - public static bool trafficLightPriorityRules = false; - public static bool banRegularTrafficOnBusLanes = false; - public static bool advancedAI = false; - public static bool realisticPublicTransport = false; - public static byte altLaneSelectionRatio = 0; - public static bool highwayRules = false; + public static bool allowEnterBlockedJunctions = false; + public static bool allowUTurns = false; + public static bool allowNearTurnOnRed = false; + public static bool allowFarTurnOnRed = false; + public static bool allowLaneChangesWhileGoingStraight = false; + public static bool trafficLightPriorityRules = false; + public static bool banRegularTrafficOnBusLanes = false; + public static bool advancedAI = false; + public static bool realisticPublicTransport = false; + public static byte altLaneSelectionRatio = 0; + public static bool highwayRules = false; #if DEBUG - public static bool showLanes = true; + public static bool showLanes = true; #else public static bool showLanes = false; #endif - public static bool strongerRoadConditionEffects = false; - public static bool prohibitPocketCars = false; - public static bool disableDespawning = false; - public static bool preferOuterLane = false; - //public static byte publicTransportUsage = 1; - - public static bool prioritySignsEnabled = true; - public static bool timedLightsEnabled = true; - public static bool customSpeedLimitsEnabled = true; - public static bool vehicleRestrictionsEnabled = true; - public static bool parkingRestrictionsEnabled = true; - public static bool junctionRestrictionsEnabled = true; - public static bool turnOnRedEnabled = true; - public static bool laneConnectorEnabled = true; + public static bool strongerRoadConditionEffects = false; + public static bool prohibitPocketCars = false; + public static bool disableDespawning = false; + public static bool preferOuterLane = false; + //public static byte publicTransportUsage = 1; + + public static bool prioritySignsEnabled = true; + public static bool timedLightsEnabled = true; + public static bool customSpeedLimitsEnabled = true; + public static bool vehicleRestrictionsEnabled = true; + public static bool parkingRestrictionsEnabled = true; + public static bool junctionRestrictionsEnabled = true; + public static bool turnOnRedEnabled = true; + public static bool laneConnectorEnabled = true; public static bool scanForKnownIncompatibleModsEnabled = true; public static bool ignoreDisabledModsEnabled = false; - public static VehicleRestrictionsAggression vehicleRestrictionsAggression = VehicleRestrictionsAggression.Medium; + public static VehicleRestrictionsAggression vehicleRestrictionsAggression = VehicleRestrictionsAggression.Medium; - public static bool MenuRebuildRequired { - get { return false; } - internal set { - if (value) { - if (LoadingExtension.BaseUI != null) { - LoadingExtension.BaseUI.RebuildMenu(); - } - } - } - } + public static bool MenuRebuildRequired { + get { return false; } + internal set { + if (value) { + if (LoadingExtension.BaseUI != null) { + LoadingExtension.BaseUI.RebuildMenu(); + } + } + } + } - public static void makeSettings(UIHelperBase helper) { - // tabbing code is borrowed from RushHour mod - // https://github.com/PropaneDragon/RushHour/blob/release/RushHour/Options/OptionHandler.cs + public static void makeSettings(UIHelperBase helper) { + // tabbing code is borrowed from RushHour mod + // https://github.com/PropaneDragon/RushHour/blob/release/RushHour/Options/OptionHandler.cs - UIHelper actualHelper = helper as UIHelper; - UIComponent container = actualHelper.self as UIComponent; + UIHelper actualHelper = helper as UIHelper; + UIComponent container = actualHelper.self as UIComponent; - UITabstrip tabStrip = container.AddUIComponent(); - tabStrip.relativePosition = new Vector3(0, 0); - tabStrip.size = new Vector2(container.width - 20, 40); + UITabstrip tabStrip = container.AddUIComponent(); + tabStrip.relativePosition = new Vector3(0, 0); + tabStrip.size = new Vector2(container.width - 20, 40); - UITabContainer tabContainer = container.AddUIComponent(); - tabContainer.relativePosition = new Vector3(0, 40); - tabContainer.size = new Vector2(container.width - 20, container.height - tabStrip.height - 20); - tabStrip.tabPages = tabContainer; + UITabContainer tabContainer = container.AddUIComponent(); + tabContainer.relativePosition = new Vector3(0, 40); + tabContainer.size = new Vector2(container.width - 20, container.height - tabStrip.height - 20); + tabStrip.tabPages = tabContainer; - int tabIndex = 0; - // GENERAL + int tabIndex = 0; + // GENERAL - AddOptionTab(tabStrip, Translation.GetString("General"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); - tabStrip.selectedIndex = tabIndex; + AddOptionTab(tabStrip, Translation.GetString("General"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); + tabStrip.selectedIndex = tabIndex; - UIPanel currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; + UIPanel currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; - UIHelper panelHelper = new UIHelper(currentPanel); + UIHelper panelHelper = new UIHelper(currentPanel); - var generalGroup = panelHelper.AddGroup(Translation.GetString("General")); + var generalGroup = panelHelper.AddGroup(Translation.GetString("General")); - string[] languageLabels = new string[Translation.AVAILABLE_LANGUAGE_CODES.Count + 1]; - languageLabels[0] = Translation.GetString("Game_language"); - for (int i = 0; i < Translation.AVAILABLE_LANGUAGE_CODES.Count; ++i) { - languageLabels[i + 1] = Translation.LANGUAGE_LABELS[Translation.AVAILABLE_LANGUAGE_CODES[i]]; - } - int languageIndex = 0; - string curLangCode = GlobalConfig.Instance.LanguageCode; - if (curLangCode != null) { - languageIndex = Translation.AVAILABLE_LANGUAGE_CODES.IndexOf(curLangCode); - if (languageIndex < 0) { - languageIndex = 0; - } else { - ++languageIndex; - } - } + string[] languageLabels = new string[Translation.AVAILABLE_LANGUAGE_CODES.Count + 1]; + languageLabels[0] = Translation.GetString("Game_language"); + for (int i = 0; i < Translation.AVAILABLE_LANGUAGE_CODES.Count; ++i) { + languageLabels[i + 1] = Translation.LANGUAGE_LABELS[Translation.AVAILABLE_LANGUAGE_CODES[i]]; + } + int languageIndex = 0; + string curLangCode = GlobalConfig.Instance.LanguageCode; + if (curLangCode != null) { + languageIndex = Translation.AVAILABLE_LANGUAGE_CODES.IndexOf(curLangCode); + if (languageIndex < 0) { + languageIndex = 0; + } else { + ++languageIndex; + } + } - languageDropdown = generalGroup.AddDropdown(Translation.GetString("Language") + ":", languageLabels, languageIndex, onLanguageChanged) as UIDropDown; - lockButtonToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_button_position"), GlobalConfig.Instance.Main.MainMenuButtonPosLocked, onLockButtonChanged) as UICheckBox; - lockMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_position"), GlobalConfig.Instance.Main.MainMenuPosLocked, onLockMenuChanged) as UICheckBox; - tinyMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Compact_main_menu"), GlobalConfig.Instance.Main.TinyMainMenu, onTinyMenuChanged) as UICheckBox; - guiTransparencySlider = generalGroup.AddSlider(Translation.GetString("Window_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.GuiTransparency, onGuiTransparencyChanged) as UISlider; - guiTransparencySlider.parent.Find("Label").width = 500; - overlayTransparencySlider = generalGroup.AddSlider(Translation.GetString("Overlay_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.OverlayTransparency, onOverlayTransparencyChanged) as UISlider; - overlayTransparencySlider.parent.Find("Label").width = 500; - enableTutorialToggle = generalGroup.AddCheckbox(Translation.GetString("Enable_tutorial_messages"), GlobalConfig.Instance.Main.EnableTutorial, onEnableTutorialsChanged) as UICheckBox; - showCompatibilityCheckErrorToggle = generalGroup.AddCheckbox(Translation.GetString("Notify_me_if_there_is_an_unexpected_mod_conflict"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; - scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; - ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; - Indent(ignoreDisabledModsToggle); - - // General: Speed Limits - setupSpeedLimitsPanel(panelHelper, generalGroup); - - // General: Simulation - var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); - simAccuracyDropdown = simGroup.AddDropdown(Translation.GetString("Simulation_accuracy") + ":", new string[] { Translation.GetString("Very_high"), Translation.GetString("High"), Translation.GetString("Medium"), Translation.GetString("Low"), Translation.GetString("Very_Low") }, simAccuracy, onSimAccuracyChanged) as UIDropDown; - instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; - - // GAMEPLAY - ++tabIndex; - - AddOptionTab(tabStrip, Translation.GetString("Gameplay")); - tabStrip.selectedIndex = tabIndex; - - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; - - panelHelper = new UIHelper(currentPanel); - - var vehBehaviorGroup = panelHelper.AddGroup(Translation.GetString("Vehicle_behavior")); - - recklessDriversDropdown = vehBehaviorGroup.AddDropdown(Translation.GetString("Reckless_driving") + ":", new string[] { Translation.GetString("Path_Of_Evil_(10_%)"), Translation.GetString("Rush_Hour_(5_%)"), Translation.GetString("Minor_Complaints_(2_%)"), Translation.GetString("Holy_City_(0_%)") }, recklessDrivers, onRecklessDriversChanged) as UIDropDown; - recklessDriversDropdown.width = 300; - individualDrivingStyleToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Individual_driving_styles"), individualDrivingStyle, onIndividualDrivingStyleChanged) as UICheckBox; - - if (SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { - strongerRoadConditionEffectsToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Road_condition_has_a_bigger_impact_on_vehicle_speed"), strongerRoadConditionEffects, onStrongerRoadConditionEffectsChanged) as UICheckBox; - } - disableDespawningToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Disable_despawning"), disableDespawning, onDisableDespawningChanged) as UICheckBox; + languageDropdown = generalGroup.AddDropdown(Translation.GetString("Language") + ":", languageLabels, languageIndex, onLanguageChanged) as UIDropDown; + lockButtonToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_button_position"), GlobalConfig.Instance.Main.MainMenuButtonPosLocked, onLockButtonChanged) as UICheckBox; + lockMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_position"), GlobalConfig.Instance.Main.MainMenuPosLocked, onLockMenuChanged) as UICheckBox; + tinyMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Compact_main_menu"), GlobalConfig.Instance.Main.TinyMainMenu, onTinyMenuChanged) as UICheckBox; + guiTransparencySlider = generalGroup.AddSlider(Translation.GetString("Window_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.GuiTransparency, onGuiTransparencyChanged) as UISlider; + guiTransparencySlider.parent.Find("Label").width = 500; + overlayTransparencySlider = generalGroup.AddSlider(Translation.GetString("Overlay_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.OverlayTransparency, onOverlayTransparencyChanged) as UISlider; + overlayTransparencySlider.parent.Find("Label").width = 500; + enableTutorialToggle = generalGroup.AddCheckbox(Translation.GetString("Enable_tutorial_messages"), GlobalConfig.Instance.Main.EnableTutorial, onEnableTutorialsChanged) as UICheckBox; + showCompatibilityCheckErrorToggle = generalGroup.AddCheckbox(Translation.GetString("Notify_me_if_there_is_an_unexpected_mod_conflict"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; + scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; + ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; + Indent(ignoreDisabledModsToggle); + + // General: Speed Limits + setupSpeedLimitsPanel(panelHelper, generalGroup); + + // General: Simulation + var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); + simAccuracyDropdown = simGroup.AddDropdown(Translation.GetString("Simulation_accuracy") + ":", new string[] { Translation.GetString("Very_high"), Translation.GetString("High"), Translation.GetString("Medium"), Translation.GetString("Low"), Translation.GetString("Very_Low") }, simAccuracy, onSimAccuracyChanged) as UIDropDown; + instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; + + // GAMEPLAY + ++tabIndex; + + AddOptionTab(tabStrip, Translation.GetString("Gameplay")); + tabStrip.selectedIndex = tabIndex; + + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; + + panelHelper = new UIHelper(currentPanel); + + var vehBehaviorGroup = panelHelper.AddGroup(Translation.GetString("Vehicle_behavior")); + + recklessDriversDropdown = vehBehaviorGroup.AddDropdown(Translation.GetString("Reckless_driving") + ":", new string[] { Translation.GetString("Path_Of_Evil_(10_%)"), Translation.GetString("Rush_Hour_(5_%)"), Translation.GetString("Minor_Complaints_(2_%)"), Translation.GetString("Holy_City_(0_%)") }, recklessDrivers, onRecklessDriversChanged) as UIDropDown; + recklessDriversDropdown.width = 300; + individualDrivingStyleToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Individual_driving_styles"), individualDrivingStyle, onIndividualDrivingStyleChanged) as UICheckBox; + + if (SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { + strongerRoadConditionEffectsToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Road_condition_has_a_bigger_impact_on_vehicle_speed"), strongerRoadConditionEffects, onStrongerRoadConditionEffectsChanged) as UICheckBox; + } + disableDespawningToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Disable_despawning"), disableDespawning, onDisableDespawningChanged) as UICheckBox; - var vehAiGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI")); - advancedAIToggle = vehAiGroup.AddCheckbox(Translation.GetString("Enable_Advanced_Vehicle_AI"), advancedAI, onAdvancedAIChanged) as UICheckBox; - altLaneSelectionRatioSlider = vehAiGroup.AddSlider(Translation.GetString("Dynamic_lane_section") + ":", 0, 100, 5, altLaneSelectionRatio, onAltLaneSelectionRatioChanged) as UISlider; - altLaneSelectionRatioSlider.parent.Find("Label").width = 450; + var vehAiGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI")); + advancedAIToggle = vehAiGroup.AddCheckbox(Translation.GetString("Enable_Advanced_Vehicle_AI"), advancedAI, onAdvancedAIChanged) as UICheckBox; + altLaneSelectionRatioSlider = vehAiGroup.AddSlider(Translation.GetString("Dynamic_lane_section") + ":", 0, 100, 5, altLaneSelectionRatio, onAltLaneSelectionRatioChanged) as UISlider; + altLaneSelectionRatioSlider.parent.Find("Label").width = 450; - var parkAiGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI")); - prohibitPocketCarsToggle = parkAiGroup.AddCheckbox(Translation.GetString("Enable_more_realistic_parking"), prohibitPocketCars, onProhibitPocketCarsChanged) as UICheckBox; + var parkAiGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI")); + prohibitPocketCarsToggle = parkAiGroup.AddCheckbox(Translation.GetString("Enable_more_realistic_parking"), prohibitPocketCars, onProhibitPocketCarsChanged) as UICheckBox; - var ptGroup = panelHelper.AddGroup(Translation.GetString("Public_transport")); - realisticPublicTransportToggle = ptGroup.AddCheckbox(Translation.GetString("Prevent_excessive_transfers_at_public_transport_stations"), realisticPublicTransport, onRealisticPublicTransportChanged) as UICheckBox; + var ptGroup = panelHelper.AddGroup(Translation.GetString("Public_transport")); + realisticPublicTransportToggle = ptGroup.AddCheckbox(Translation.GetString("Prevent_excessive_transfers_at_public_transport_stations"), realisticPublicTransport, onRealisticPublicTransportChanged) as UICheckBox; - // VEHICLE RESTRICTIONS - ++tabIndex; + // VEHICLE RESTRICTIONS + ++tabIndex; - AddOptionTab(tabStrip, Translation.GetString("Policies_&_Restrictions")); - tabStrip.selectedIndex = tabIndex; + AddOptionTab(tabStrip, Translation.GetString("Policies_&_Restrictions")); + tabStrip.selectedIndex = tabIndex; - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; - panelHelper = new UIHelper(currentPanel); + panelHelper = new UIHelper(currentPanel); - var atJunctionsGroup = panelHelper.AddGroup(Translation.GetString("At_junctions")); + var atJunctionsGroup = panelHelper.AddGroup(Translation.GetString("At_junctions")); #if DEBUG - allRelaxedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("All_vehicles_may_ignore_lane_arrows"), allRelaxed, onAllRelaxedChanged) as UICheckBox; + allRelaxedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("All_vehicles_may_ignore_lane_arrows"), allRelaxed, onAllRelaxedChanged) as UICheckBox; #endif relaxedBussesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Busses_may_ignore_lane_arrows"), relaxedBusses, onRelaxedBussesChanged) as UICheckBox; allowEnterBlockedJunctionsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_enter_blocked_junctions"), allowEnterBlockedJunctions, onAllowEnterBlockedJunctionsChanged) as UICheckBox; - allowUTurnsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_do_u-turns_at_junctions"), allowUTurns, onAllowUTurnsChanged) as UICheckBox; - allowNearTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_turn_on_red"), allowNearTurnOnRed, onAllowNearTurnOnRedChanged) as UICheckBox; - allowFarTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Also_apply_to_left/right_turns_between_one-way_streets"), allowFarTurnOnRed, onAllowFarTurnOnRedChanged) as UICheckBox; - allowLaneChangesWhileGoingStraightToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_going_straight_may_change_lanes_at_junctions"), allowLaneChangesWhileGoingStraight, onAllowLaneChangesWhileGoingStraightChanged) as UICheckBox; - trafficLightPriorityRulesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights"), trafficLightPriorityRules, onTrafficLightPriorityRulesChanged) as UICheckBox; - - Indent(allowFarTurnOnRedToggle); - - var onRoadsGroup = panelHelper.AddGroup(Translation.GetString("On_roads")); - vehicleRestrictionsAggressionDropdown = onRoadsGroup.AddDropdown(Translation.GetString("Vehicle_restrictions_aggression") + ":", new string[] { Translation.GetString("Low"), Translation.GetString("Medium"), Translation.GetString("High"), Translation.GetString("Strict") }, (int)vehicleRestrictionsAggression, onVehicleRestrictionsAggressionChanged) as UIDropDown; - banRegularTrafficOnBusLanesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Ban_private_cars_and_trucks_on_bus_lanes"), banRegularTrafficOnBusLanes, onBanRegularTrafficOnBusLanesChanged) as UICheckBox; - highwayRulesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Enable_highway_specific_lane_merging/splitting_rules"), highwayRules, onHighwayRulesChanged) as UICheckBox; - preferOuterLaneToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Heavy_trucks_prefer_outer_lanes_on_highways"), preferOuterLane, onPreferOuterLaneChanged) as UICheckBox; - - if (SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) { - var inCaseOfEmergencyGroup = panelHelper.AddGroup(Translation.GetString("In_case_of_emergency")); - evacBussesMayIgnoreRulesToggle = inCaseOfEmergencyGroup.AddCheckbox(Translation.GetString("Evacuation_busses_may_ignore_traffic_rules"), evacBussesMayIgnoreRules, onEvacBussesMayIgnoreRulesChanged) as UICheckBox; - } + allowUTurnsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_do_u-turns_at_junctions"), allowUTurns, onAllowUTurnsChanged) as UICheckBox; + allowNearTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_turn_on_red"), allowNearTurnOnRed, onAllowNearTurnOnRedChanged) as UICheckBox; + allowFarTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Also_apply_to_left/right_turns_between_one-way_streets"), allowFarTurnOnRed, onAllowFarTurnOnRedChanged) as UICheckBox; + allowLaneChangesWhileGoingStraightToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_going_straight_may_change_lanes_at_junctions"), allowLaneChangesWhileGoingStraight, onAllowLaneChangesWhileGoingStraightChanged) as UICheckBox; + trafficLightPriorityRulesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights"), trafficLightPriorityRules, onTrafficLightPriorityRulesChanged) as UICheckBox; + + Indent(allowFarTurnOnRedToggle); + + var onRoadsGroup = panelHelper.AddGroup(Translation.GetString("On_roads")); + vehicleRestrictionsAggressionDropdown = onRoadsGroup.AddDropdown(Translation.GetString("Vehicle_restrictions_aggression") + ":", new string[] { Translation.GetString("Low"), Translation.GetString("Medium"), Translation.GetString("High"), Translation.GetString("Strict") }, (int)vehicleRestrictionsAggression, onVehicleRestrictionsAggressionChanged) as UIDropDown; + banRegularTrafficOnBusLanesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Ban_private_cars_and_trucks_on_bus_lanes"), banRegularTrafficOnBusLanes, onBanRegularTrafficOnBusLanesChanged) as UICheckBox; + highwayRulesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Enable_highway_specific_lane_merging/splitting_rules"), highwayRules, onHighwayRulesChanged) as UICheckBox; + preferOuterLaneToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Heavy_trucks_prefer_outer_lanes_on_highways"), preferOuterLane, onPreferOuterLaneChanged) as UICheckBox; + + if (SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) { + var inCaseOfEmergencyGroup = panelHelper.AddGroup(Translation.GetString("In_case_of_emergency")); + evacBussesMayIgnoreRulesToggle = inCaseOfEmergencyGroup.AddCheckbox(Translation.GetString("Evacuation_busses_may_ignore_traffic_rules"), evacBussesMayIgnoreRules, onEvacBussesMayIgnoreRulesChanged) as UICheckBox; + } - // OVERLAYS - ++tabIndex; - - AddOptionTab(tabStrip, Translation.GetString("Overlays")); - tabStrip.selectedIndex = tabIndex; - - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; - - panelHelper = new UIHelper(currentPanel); - - prioritySignsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsOverlay, onPrioritySignsOverlayChanged) as UICheckBox; - timedLightsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsOverlay, onTimedLightsOverlayChanged) as UICheckBox; - speedLimitsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Speed_limits"), speedLimitsOverlay, onSpeedLimitsOverlayChanged) as UICheckBox; - vehicleRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsOverlay, onVehicleRestrictionsOverlayChanged) as UICheckBox; - parkingRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsOverlay, onParkingRestrictionsOverlayChanged) as UICheckBox; - junctionRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsOverlay, onJunctionRestrictionsOverlayChanged) as UICheckBox; - connectedLanesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Connected_lanes"), connectedLanesOverlay, onConnectedLanesOverlayChanged) as UICheckBox; - nodesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Nodes_and_segments"), nodesOverlay, onNodesOverlayChanged) as UICheckBox; - showLanesToggle = panelHelper.AddCheckbox(Translation.GetString("Lanes"), showLanes, onShowLanesChanged) as UICheckBox; + // OVERLAYS + ++tabIndex; + + AddOptionTab(tabStrip, Translation.GetString("Overlays")); + tabStrip.selectedIndex = tabIndex; + + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; + + panelHelper = new UIHelper(currentPanel); + + prioritySignsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsOverlay, onPrioritySignsOverlayChanged) as UICheckBox; + timedLightsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsOverlay, onTimedLightsOverlayChanged) as UICheckBox; + speedLimitsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Speed_limits"), speedLimitsOverlay, onSpeedLimitsOverlayChanged) as UICheckBox; + vehicleRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsOverlay, onVehicleRestrictionsOverlayChanged) as UICheckBox; + parkingRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsOverlay, onParkingRestrictionsOverlayChanged) as UICheckBox; + junctionRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsOverlay, onJunctionRestrictionsOverlayChanged) as UICheckBox; + connectedLanesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Connected_lanes"), connectedLanesOverlay, onConnectedLanesOverlayChanged) as UICheckBox; + nodesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Nodes_and_segments"), nodesOverlay, onNodesOverlayChanged) as UICheckBox; + showLanesToggle = panelHelper.AddCheckbox(Translation.GetString("Lanes"), showLanes, onShowLanesChanged) as UICheckBox; #if DEBUG - vehicleOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicles"), vehicleOverlay, onVehicleOverlayChanged) as UICheckBox; - citizenOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Citizens"), citizenOverlay, onCitizenOverlayChanged) as UICheckBox; - buildingOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Buildings"), buildingOverlay, onBuildingOverlayChanged) as UICheckBox; + vehicleOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicles"), vehicleOverlay, onVehicleOverlayChanged) as UICheckBox; + citizenOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Citizens"), citizenOverlay, onCitizenOverlayChanged) as UICheckBox; + buildingOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Buildings"), buildingOverlay, onBuildingOverlayChanged) as UICheckBox; #endif - // MAINTENANCE - ++tabIndex; + // MAINTENANCE + ++tabIndex; - AddOptionTab(tabStrip, Translation.GetString("Maintenance")); - tabStrip.selectedIndex = tabIndex; + AddOptionTab(tabStrip, Translation.GetString("Maintenance")); + tabStrip.selectedIndex = tabIndex; - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; - panelHelper = new UIHelper(currentPanel); + panelHelper = new UIHelper(currentPanel); - var maintenanceGroup = panelHelper.AddGroup(Translation.GetString("Maintenance")); + var maintenanceGroup = panelHelper.AddGroup(Translation.GetString("Maintenance")); - resetStuckEntitiesBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_stuck_cims_and_vehicles"), onClickResetStuckEntities) as UIButton; - removeParkedVehiclesBtn = maintenanceGroup.AddButton(Translation.GetString("Remove_parked_vehicles"), onClickRemoveParkedVehicles) as UIButton; + resetStuckEntitiesBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_stuck_cims_and_vehicles"), onClickResetStuckEntities) as UIButton; + removeParkedVehiclesBtn = maintenanceGroup.AddButton(Translation.GetString("Remove_parked_vehicles"), onClickRemoveParkedVehicles) as UIButton; #if DEBUG - resetSpeedLimitsBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_custom_speed_limits"), onClickResetSpeedLimits) as UIButton; + resetSpeedLimitsBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_custom_speed_limits"), onClickResetSpeedLimits) as UIButton; #endif - reloadGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reload_global_configuration"), onClickReloadGlobalConf) as UIButton; - resetGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_global_configuration"), onClickResetGlobalConf) as UIButton; + reloadGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reload_global_configuration"), onClickReloadGlobalConf) as UIButton; + resetGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_global_configuration"), onClickResetGlobalConf) as UIButton; #if QUEUEDSTATS - showPathFindStatsToggle = maintenanceGroup.AddCheckbox(Translation.GetString("Show_path-find_stats"), showPathFindStats, onShowPathFindStatsChanged) as UICheckBox; + showPathFindStatsToggle = maintenanceGroup.AddCheckbox(Translation.GetString("Show_path-find_stats"), showPathFindStats, onShowPathFindStatsChanged) as UICheckBox; #endif - var featureGroup = panelHelper.AddGroup(Translation.GetString("Activated_features")) as UIHelper; - enablePrioritySignsToggle = featureGroup.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsEnabled, onPrioritySignsEnabledChanged) as UICheckBox; - enableTimedLightsToggle = featureGroup.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsEnabled, onTimedLightsEnabledChanged) as UICheckBox; - enableCustomSpeedLimitsToggle = featureGroup.AddCheckbox(Translation.GetString("Speed_limits"), customSpeedLimitsEnabled, onCustomSpeedLimitsEnabledChanged) as UICheckBox; - enableVehicleRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsEnabled, onVehicleRestrictionsEnabledChanged) as UICheckBox; - enableParkingRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsEnabled, onParkingRestrictionsEnabledChanged) as UICheckBox; - enableJunctionRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsEnabled, onJunctionRestrictionsEnabledChanged) as UICheckBox; - turnOnRedEnabledToggle = featureGroup.AddCheckbox(Translation.GetString("Turn_on_red"), turnOnRedEnabled, onTurnOnRedEnabledChanged) as UICheckBox; - enableLaneConnectorToggle = featureGroup.AddCheckbox(Translation.GetString("Lane_connector"), laneConnectorEnabled, onLaneConnectorEnabledChanged) as UICheckBox; + var featureGroup = panelHelper.AddGroup(Translation.GetString("Activated_features")) as UIHelper; + enablePrioritySignsToggle = featureGroup.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsEnabled, onPrioritySignsEnabledChanged) as UICheckBox; + enableTimedLightsToggle = featureGroup.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsEnabled, onTimedLightsEnabledChanged) as UICheckBox; + enableCustomSpeedLimitsToggle = featureGroup.AddCheckbox(Translation.GetString("Speed_limits"), customSpeedLimitsEnabled, onCustomSpeedLimitsEnabledChanged) as UICheckBox; + enableVehicleRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsEnabled, onVehicleRestrictionsEnabledChanged) as UICheckBox; + enableParkingRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsEnabled, onParkingRestrictionsEnabledChanged) as UICheckBox; + enableJunctionRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsEnabled, onJunctionRestrictionsEnabledChanged) as UICheckBox; + turnOnRedEnabledToggle = featureGroup.AddCheckbox(Translation.GetString("Turn_on_red"), turnOnRedEnabled, onTurnOnRedEnabledChanged) as UICheckBox; + enableLaneConnectorToggle = featureGroup.AddCheckbox(Translation.GetString("Lane_connector"), laneConnectorEnabled, onLaneConnectorEnabledChanged) as UICheckBox; - Indent(turnOnRedEnabledToggle); + Indent(turnOnRedEnabledToggle); - // KEYBOARD - ++tabIndex; + // KEYBOARD + ++tabIndex; - AddOptionTab(tabStrip, Translation.GetString("Keybinds")); - tabStrip.selectedIndex = tabIndex; + AddOptionTab(tabStrip, Translation.GetString("Keybinds")); + tabStrip.selectedIndex = tabIndex; - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; - panelHelper = new UIHelper(currentPanel); + panelHelper = new UIHelper(currentPanel); - var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); - ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); + var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); + ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); #if DEBUG - // GLOBAL CONFIG - /* - AddOptionTab(tabStrip, Translation.GetString("Global_configuration"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); - tabStrip.selectedIndex = tabIndex; - - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; - - panelHelper = new UIHelper(currentPanel); - - GlobalConfig globalConf = GlobalConfig.Instance; - - var aiTrafficMeasurementConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("General")); - - aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Live_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxTrafficBuffer, onMaxTrafficBufferChanged); - aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Path-find_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxPathFindTrafficBuffer, onMaxPathFindTrafficBufferChanged); - aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Max._congestion_measurements"), 0f, 1000f, 10f, globalConf.MaxNumCongestionMeasurements, onMaxNumCongestionMeasurementsChanged); - - var aiLaneSelParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_parameters")); - - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_randomization"), 0f, 100f, 1f, globalConf.LaneDensityRandInterval, onLaneDensityRandIntervalChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_discretization"), 0f, 100f, 1f, globalConf.LaneDensityDiscretization, onLaneTrafficDiscretizationChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_randomization"), 0f, 100f, 1f, globalConf.LaneUsageRandInterval, onLaneUsageRandIntervalChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_discretization"), 0f, 100f, 1f, globalConf.LaneUsageDiscretization, onLaneUsageDiscretizationChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Congestion_rel._velocity_threshold"), 0f, 100f, 1f, globalConf.CongestionSqrSpeedThreshold, onCongestionSqrSpeedThresholdChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Max._walking_distance"), 0f, 10000f, 100f, globalConf.MaxWalkingDistance, onMaxWalkingDistanceChanged); - - var aiLaneSelFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_factors")); - - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Spread_randomization_factor"), 1f, 5f, 0.05f, globalConf.UsageCostFactor, onUsageCostFactorChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Traffic_avoidance_factor"), 1f, 5f, 0.05f, globalConf.TrafficCostFactor, onTrafficCostFactorChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_penalty"), 1f, 50f, 0.5f, globalConf.PublicTransportLanePenalty, onPublicTransportLanePenaltyChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_reward"), 0f, 1f, 0.05f, globalConf.PublicTransportLaneReward, onPublicTransportLaneRewardChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Heavy_vehicle_max._inner_lane_penalty"), 0f, 5f, 0.05f, globalConf.HeavyVehicleMaxInnerLanePenalty, onHeavyVehicleMaxInnerLanePenaltyChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Vehicle_restrictions_penalty"), 0f, 1000f, 25f, globalConf.VehicleRestrictionsPenalty, onVehicleRestrictionsPenaltyChanged); - - var aiLaneChangeParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_parameters")); - - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("U-turn_lane_distance"), 1f, 5f, 1f, globalConf.UturnLaneDistance, onUturnLaneDistanceChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Incompatible_lane_distance"), 1f, 5f, 1f, globalConf.IncompatibleLaneDistance, onIncompatibleLaneDistanceChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Lane_changing_randomization_modulo"), 1f, 100f, 1f, globalConf.RandomizedLaneChangingModulo, onRandomizedLaneChangingModuloChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Min._controlled_segments_in_front_of_highway_interchanges"), 1f, 10f, 1f, globalConf.MinHighwayInterchangeSegments, onMinHighwayInterchangeSegmentsChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Max._controlled_segments_in_front_of_highway_interchanges"), 1f, 30f, 1f, globalConf.MaxHighwayInterchangeSegments, onMaxHighwayInterchangeSegmentsChanged); - - var aiLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_cost_factors")); - - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_city_roads"), 1f, 5f, 0.05f, globalConf.CityRoadLaneChangingBaseCost, onCityRoadLaneChangingBaseCostChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_highways"), 1f, 5f, 0.05f, globalConf.HighwayLaneChangingBaseCost, onHighwayLaneChangingBaseCostChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("In_front_of_highway_interchanges"), 1f, 5f, 0.05f, globalConf.HighwayInterchangeLaneChangingBaseCost, onHighwayInterchangeLaneChangingBaseCostChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("For_heavy_vehicles"), 1f, 5f, 0.05f, globalConf.HeavyVehicleLaneChangingCostFactor, onHeavyVehicleLaneChangingCostFactorChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_congested_roads"), 1f, 5f, 0.05f, globalConf.CongestionLaneChangingCostFactor, onCongestionLaneChangingCostFactorChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("When_changing_multiple_lanes_at_once"), 1f, 5f, 0.05f, globalConf.MoreThanOneLaneChangingCostFactor, onMoreThanOneLaneChangingCostFactorChanged); - - var aiParkingLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI") + ": " + Translation.GetString("General")); - */ - - // DEBUG - /*++tabIndex; - - settingsButton = tabStrip.AddTab("Debug", tabTemplate, true); - settingsButton.textPadding = new RectOffset(10, 10, 10, 10); - settingsButton.autoSize = true; - settingsButton.tooltip = "Debug"; - - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; - - panelHelper = new UIHelper(currentPanel); - - debugSwitchFields.Clear(); - for (int i = 0; i < Debug.Switches.Length; ++i) { - int index = i; - string varName = $"Debug switch #{i}"; - debugSwitchFields.Add(panelHelper.AddCheckbox(varName, Debug.Switches[i], delegate (bool newVal) { onBoolValueChanged(varName, newVal, ref Debug.Switches[index]); }) as UICheckBox); - } + // GLOBAL CONFIG + /* + AddOptionTab(tabStrip, Translation.GetString("Global_configuration"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); + tabStrip.selectedIndex = tabIndex; + + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; + + panelHelper = new UIHelper(currentPanel); + + GlobalConfig globalConf = GlobalConfig.Instance; + + var aiTrafficMeasurementConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("General")); - debugValueFields.Clear(); - for (int i = 0; i < debugValues.Length; ++i) { - int index = i; - string varName = $"Debug value #{i}"; - debugValueFields.Add(panelHelper.AddTextfield(varName, String.Format("{0:0.##}", debugValues[i]), delegate(string newValStr) { onFloatValueChanged(varName, newValStr, ref debugValues[index]); }, null) as UITextField); - }*/ + aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Live_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxTrafficBuffer, onMaxTrafficBufferChanged); + aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Path-find_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxPathFindTrafficBuffer, onMaxPathFindTrafficBufferChanged); + aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Max._congestion_measurements"), 0f, 1000f, 10f, globalConf.MaxNumCongestionMeasurements, onMaxNumCongestionMeasurementsChanged); + + var aiLaneSelParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_parameters")); + + aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_randomization"), 0f, 100f, 1f, globalConf.LaneDensityRandInterval, onLaneDensityRandIntervalChanged); + aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_discretization"), 0f, 100f, 1f, globalConf.LaneDensityDiscretization, onLaneTrafficDiscretizationChanged); + aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_randomization"), 0f, 100f, 1f, globalConf.LaneUsageRandInterval, onLaneUsageRandIntervalChanged); + aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_discretization"), 0f, 100f, 1f, globalConf.LaneUsageDiscretization, onLaneUsageDiscretizationChanged); + aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Congestion_rel._velocity_threshold"), 0f, 100f, 1f, globalConf.CongestionSqrSpeedThreshold, onCongestionSqrSpeedThresholdChanged); + aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Max._walking_distance"), 0f, 10000f, 100f, globalConf.MaxWalkingDistance, onMaxWalkingDistanceChanged); + + var aiLaneSelFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_factors")); + + aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Spread_randomization_factor"), 1f, 5f, 0.05f, globalConf.UsageCostFactor, onUsageCostFactorChanged); + aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Traffic_avoidance_factor"), 1f, 5f, 0.05f, globalConf.TrafficCostFactor, onTrafficCostFactorChanged); + aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_penalty"), 1f, 50f, 0.5f, globalConf.PublicTransportLanePenalty, onPublicTransportLanePenaltyChanged); + aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_reward"), 0f, 1f, 0.05f, globalConf.PublicTransportLaneReward, onPublicTransportLaneRewardChanged); + aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Heavy_vehicle_max._inner_lane_penalty"), 0f, 5f, 0.05f, globalConf.HeavyVehicleMaxInnerLanePenalty, onHeavyVehicleMaxInnerLanePenaltyChanged); + aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Vehicle_restrictions_penalty"), 0f, 1000f, 25f, globalConf.VehicleRestrictionsPenalty, onVehicleRestrictionsPenaltyChanged); + + var aiLaneChangeParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_parameters")); + + aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("U-turn_lane_distance"), 1f, 5f, 1f, globalConf.UturnLaneDistance, onUturnLaneDistanceChanged); + aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Incompatible_lane_distance"), 1f, 5f, 1f, globalConf.IncompatibleLaneDistance, onIncompatibleLaneDistanceChanged); + aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Lane_changing_randomization_modulo"), 1f, 100f, 1f, globalConf.RandomizedLaneChangingModulo, onRandomizedLaneChangingModuloChanged); + aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Min._controlled_segments_in_front_of_highway_interchanges"), 1f, 10f, 1f, globalConf.MinHighwayInterchangeSegments, onMinHighwayInterchangeSegmentsChanged); + aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Max._controlled_segments_in_front_of_highway_interchanges"), 1f, 30f, 1f, globalConf.MaxHighwayInterchangeSegments, onMaxHighwayInterchangeSegmentsChanged); + + var aiLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_cost_factors")); + + aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_city_roads"), 1f, 5f, 0.05f, globalConf.CityRoadLaneChangingBaseCost, onCityRoadLaneChangingBaseCostChanged); + aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_highways"), 1f, 5f, 0.05f, globalConf.HighwayLaneChangingBaseCost, onHighwayLaneChangingBaseCostChanged); + aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("In_front_of_highway_interchanges"), 1f, 5f, 0.05f, globalConf.HighwayInterchangeLaneChangingBaseCost, onHighwayInterchangeLaneChangingBaseCostChanged); + aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("For_heavy_vehicles"), 1f, 5f, 0.05f, globalConf.HeavyVehicleLaneChangingCostFactor, onHeavyVehicleLaneChangingCostFactorChanged); + aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_congested_roads"), 1f, 5f, 0.05f, globalConf.CongestionLaneChangingCostFactor, onCongestionLaneChangingCostFactorChanged); + aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("When_changing_multiple_lanes_at_once"), 1f, 5f, 0.05f, globalConf.MoreThanOneLaneChangingCostFactor, onMoreThanOneLaneChangingCostFactorChanged); + + var aiParkingLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI") + ": " + Translation.GetString("General")); + */ + + // DEBUG + /*++tabIndex; + + settingsButton = tabStrip.AddTab("Debug", tabTemplate, true); + settingsButton.textPadding = new RectOffset(10, 10, 10, 10); + settingsButton.autoSize = true; + settingsButton.tooltip = "Debug"; + + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; + + panelHelper = new UIHelper(currentPanel); + + debugSwitchFields.Clear(); + for (int i = 0; i < Debug.Switches.Length; ++i) { + int index = i; + string varName = $"Debug switch #{i}"; + debugSwitchFields.Add(panelHelper.AddCheckbox(varName, Debug.Switches[i], delegate (bool newVal) { onBoolValueChanged(varName, newVal, ref Debug.Switches[index]); }) as UICheckBox); + } + + debugValueFields.Clear(); + for (int i = 0; i < debugValues.Length; ++i) { + int index = i; + string varName = $"Debug value #{i}"; + debugValueFields.Add(panelHelper.AddTextfield(varName, String.Format("{0:0.##}", debugValues[i]), delegate(string newValStr) { onFloatValueChanged(varName, newValStr, ref debugValues[index]); }, null) as UITextField); + }*/ #endif - tabStrip.selectedIndex = 0; - } - - private static void setupSpeedLimitsPanel(UIHelper panelHelper, UIHelperBase generalGroup) { - displayMphToggle = generalGroup.AddCheckbox( - Translation.GetString("Display_speed_limits_mph"), - GlobalConfig.Instance.Main.DisplaySpeedLimitsMph, - onDisplayMphChanged) as UICheckBox; - var mphThemeOptions = new[] { - Translation.GetString("theme_Square_US"), - Translation.GetString("theme_Round_UK"), - Translation.GetString("theme_Round_German"), - }; - roadSignMphStyleInt = (int)GlobalConfig.Instance.Main.MphRoadSignStyle; - roadSignsMphThemeDropdown = generalGroup.AddDropdown( - Translation.GetString("Road_signs_theme_mph") + ":", - mphThemeOptions, roadSignMphStyleInt, - onRoadSignsMphThemeChanged) as UIDropDown; - roadSignsMphThemeDropdown.width = 400; - } - - private static void Indent(T component) where T : UIComponent { + tabStrip.selectedIndex = 0; + } + + private static void setupSpeedLimitsPanel(UIHelper panelHelper, UIHelperBase generalGroup) { + displayMphToggle = generalGroup.AddCheckbox( + Translation.GetString("Display_speed_limits_mph"), + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph, + onDisplayMphChanged) as UICheckBox; + var mphThemeOptions = new[] { + Translation.GetString("theme_Square_US"), + Translation.GetString("theme_Round_UK"), + Translation.GetString("theme_Round_German"), + }; + roadSignMphStyleInt = (int)GlobalConfig.Instance.Main.MphRoadSignStyle; + roadSignsMphThemeDropdown = generalGroup.AddDropdown( + Translation.GetString("Road_signs_theme_mph") + ":", + mphThemeOptions, roadSignMphStyleInt, + onRoadSignsMphThemeChanged) as UIDropDown; + roadSignsMphThemeDropdown.width = 400; + } + + private static void Indent(T component) where T : UIComponent { UILabel label = component.Find("Label"); if (label != null) { label.padding = new RectOffset(22, 0, 0, 0); @@ -537,193 +532,193 @@ private static void Indent(T component) where T : UIComponent { if (check != null) { check.relativePosition += new Vector3(22.0f, 0); } - } + } - private static UIButton AddOptionTab(UITabstrip tabStrip, string caption) { - UIButton tabButton = tabStrip.AddTab(caption); + private static UIButton AddOptionTab(UITabstrip tabStrip, string caption) { + UIButton tabButton = tabStrip.AddTab(caption); - tabButton.normalBgSprite = "SubBarButtonBase"; - tabButton.disabledBgSprite = "SubBarButtonBaseDisabled"; - tabButton.focusedBgSprite = "SubBarButtonBaseFocused"; - tabButton.hoveredBgSprite = "SubBarButtonBaseHovered"; - tabButton.pressedBgSprite = "SubBarButtonBasePressed"; + tabButton.normalBgSprite = "SubBarButtonBase"; + tabButton.disabledBgSprite = "SubBarButtonBaseDisabled"; + tabButton.focusedBgSprite = "SubBarButtonBaseFocused"; + tabButton.hoveredBgSprite = "SubBarButtonBaseHovered"; + tabButton.pressedBgSprite = "SubBarButtonBasePressed"; - tabButton.textPadding = new RectOffset(10, 10, 10, 10); - tabButton.autoSize = true; - tabButton.tooltip = caption; + tabButton.textPadding = new RectOffset(10, 10, 10, 10); + tabButton.autoSize = true; + tabButton.tooltip = caption; - return tabButton; - } + return tabButton; + } - private static bool checkGameLoaded() { - if (!SerializableDataExtension.StateLoading && !LoadingExtension.IsGameLoaded) { - UIView.library.ShowModal("ExceptionPanel").SetMessage("Nope!", Translation.GetString("Settings_are_defined_for_each_savegame_separately") + ". https://www.viathinksoft.de/tmpe/#options", false); - return false; - } - return true; - } + private static bool checkGameLoaded() { + if (!SerializableDataExtension.StateLoading && !LoadingExtension.IsGameLoaded) { + UIView.library.ShowModal("ExceptionPanel").SetMessage("Nope!", Translation.GetString("Settings_are_defined_for_each_savegame_separately") + ". https://www.viathinksoft.de/tmpe/#options", false); + return false; + } + return true; + } - private static void onGuiTransparencyChanged(float newVal) { - if (!checkGameLoaded()) - return; + private static void onGuiTransparencyChanged(float newVal) { + if (!checkGameLoaded()) + return; - setGuiTransparency((byte)Mathf.RoundToInt(newVal)); - guiTransparencySlider.tooltip = Translation.GetString("Window_transparency") + ": " + GlobalConfig.Instance.Main.GuiTransparency + " %"; + setGuiTransparency((byte)Mathf.RoundToInt(newVal)); + guiTransparencySlider.tooltip = Translation.GetString("Window_transparency") + ": " + GlobalConfig.Instance.Main.GuiTransparency + " %"; - GlobalConfig.WriteConfig(); + GlobalConfig.WriteConfig(); - Log._Debug($"GuiTransparency changed to {GlobalConfig.Instance.Main.GuiTransparency}"); - } + Log._Debug($"GuiTransparency changed to {GlobalConfig.Instance.Main.GuiTransparency}"); + } - private static void onOverlayTransparencyChanged(float newVal) { - if (!checkGameLoaded()) - return; + private static void onOverlayTransparencyChanged(float newVal) { + if (!checkGameLoaded()) + return; - setOverlayTransparency((byte)Mathf.RoundToInt(newVal)); - overlayTransparencySlider.tooltip = Translation.GetString("Overlay_transparency") + ": " + GlobalConfig.Instance.Main.OverlayTransparency + " %"; + setOverlayTransparency((byte)Mathf.RoundToInt(newVal)); + overlayTransparencySlider.tooltip = Translation.GetString("Overlay_transparency") + ": " + GlobalConfig.Instance.Main.OverlayTransparency + " %"; - GlobalConfig.WriteConfig(); + GlobalConfig.WriteConfig(); - Log._Debug($"OverlayTransparency changed to {GlobalConfig.Instance.Main.OverlayTransparency}"); - } + Log._Debug($"OverlayTransparency changed to {GlobalConfig.Instance.Main.OverlayTransparency}"); + } - private static void onAltLaneSelectionRatioChanged(float newVal) { - if (!checkGameLoaded()) - return; + private static void onAltLaneSelectionRatioChanged(float newVal) { + if (!checkGameLoaded()) + return; - setAltLaneSelectionRatio((byte)Mathf.RoundToInt(newVal)); - altLaneSelectionRatioSlider.tooltip = Translation.GetString("Percentage_of_vehicles_performing_dynamic_lane_section") + ": " + altLaneSelectionRatio + " %"; + setAltLaneSelectionRatio((byte)Mathf.RoundToInt(newVal)); + altLaneSelectionRatioSlider.tooltip = Translation.GetString("Percentage_of_vehicles_performing_dynamic_lane_section") + ": " + altLaneSelectionRatio + " %"; - Log._Debug($"altLaneSelectionRatio changed to {altLaneSelectionRatio}"); - } + Log._Debug($"altLaneSelectionRatio changed to {altLaneSelectionRatio}"); + } - private static void onPrioritySignsOverlayChanged(bool newPrioritySignsOverlay) { - if (!checkGameLoaded()) - return; + private static void onPrioritySignsOverlayChanged(bool newPrioritySignsOverlay) { + if (!checkGameLoaded()) + return; - Log._Debug($"prioritySignsOverlay changed to {newPrioritySignsOverlay}"); - prioritySignsOverlay = newPrioritySignsOverlay; + Log._Debug($"prioritySignsOverlay changed to {newPrioritySignsOverlay}"); + prioritySignsOverlay = newPrioritySignsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onTimedLightsOverlayChanged(bool newTimedLightsOverlay) { - if (!checkGameLoaded()) - return; + private static void onTimedLightsOverlayChanged(bool newTimedLightsOverlay) { + if (!checkGameLoaded()) + return; - Log._Debug($"timedLightsOverlay changed to {newTimedLightsOverlay}"); - timedLightsOverlay = newTimedLightsOverlay; + Log._Debug($"timedLightsOverlay changed to {newTimedLightsOverlay}"); + timedLightsOverlay = newTimedLightsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onSpeedLimitsOverlayChanged(bool newSpeedLimitsOverlay) { - if (!checkGameLoaded()) - return; + private static void onSpeedLimitsOverlayChanged(bool newSpeedLimitsOverlay) { + if (!checkGameLoaded()) + return; - Log._Debug($"speedLimitsOverlay changed to {newSpeedLimitsOverlay}"); - speedLimitsOverlay = newSpeedLimitsOverlay; + Log._Debug($"speedLimitsOverlay changed to {newSpeedLimitsOverlay}"); + speedLimitsOverlay = newSpeedLimitsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onVehicleRestrictionsOverlayChanged(bool newVehicleRestrictionsOverlay) { - if (!checkGameLoaded()) - return; + private static void onVehicleRestrictionsOverlayChanged(bool newVehicleRestrictionsOverlay) { + if (!checkGameLoaded()) + return; - Log._Debug($"vehicleRestrictionsOverlay changed to {newVehicleRestrictionsOverlay}"); - vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; + Log._Debug($"vehicleRestrictionsOverlay changed to {newVehicleRestrictionsOverlay}"); + vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onParkingRestrictionsOverlayChanged(bool newParkingRestrictionsOverlay) { - if (!checkGameLoaded()) - return; + private static void onParkingRestrictionsOverlayChanged(bool newParkingRestrictionsOverlay) { + if (!checkGameLoaded()) + return; - Log._Debug($"parkingRestrictionsOverlay changed to {newParkingRestrictionsOverlay}"); - parkingRestrictionsOverlay = newParkingRestrictionsOverlay; + Log._Debug($"parkingRestrictionsOverlay changed to {newParkingRestrictionsOverlay}"); + parkingRestrictionsOverlay = newParkingRestrictionsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onJunctionRestrictionsOverlayChanged(bool newValue) { - if (!checkGameLoaded()) - return; + private static void onJunctionRestrictionsOverlayChanged(bool newValue) { + if (!checkGameLoaded()) + return; - Log._Debug($"junctionRestrictionsOverlay changed to {newValue}"); - junctionRestrictionsOverlay = newValue; + Log._Debug($"junctionRestrictionsOverlay changed to {newValue}"); + junctionRestrictionsOverlay = newValue; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onConnectedLanesOverlayChanged(bool newValue) { - if (!checkGameLoaded()) - return; + private static void onConnectedLanesOverlayChanged(bool newValue) { + if (!checkGameLoaded()) + return; - Log._Debug($"connectedLanesOverlay changed to {newValue}"); - connectedLanesOverlay = newValue; + Log._Debug($"connectedLanesOverlay changed to {newValue}"); + connectedLanesOverlay = newValue; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onLanguageChanged(int newLanguageIndex) { - bool localeChanged = false; + private static void onLanguageChanged(int newLanguageIndex) { + bool localeChanged = false; - if (newLanguageIndex <= 0) { - GlobalConfig.Instance.LanguageCode = null; - GlobalConfig.WriteConfig(); - MenuRebuildRequired = true; - localeChanged = true; - } else if (newLanguageIndex - 1 < Translation.AVAILABLE_LANGUAGE_CODES.Count) { - GlobalConfig.Instance.LanguageCode = Translation.AVAILABLE_LANGUAGE_CODES[newLanguageIndex - 1]; - GlobalConfig.WriteConfig(); - MenuRebuildRequired = true; - localeChanged = true; - } else { - Log.Warning($"Options.onLanguageChanged: Invalid language index: {newLanguageIndex}"); - } + if (newLanguageIndex <= 0) { + GlobalConfig.Instance.LanguageCode = null; + GlobalConfig.WriteConfig(); + MenuRebuildRequired = true; + localeChanged = true; + } else if (newLanguageIndex - 1 < Translation.AVAILABLE_LANGUAGE_CODES.Count) { + GlobalConfig.Instance.LanguageCode = Translation.AVAILABLE_LANGUAGE_CODES[newLanguageIndex - 1]; + GlobalConfig.WriteConfig(); + MenuRebuildRequired = true; + localeChanged = true; + } else { + Log.Warning($"Options.onLanguageChanged: Invalid language index: {newLanguageIndex}"); + } - if (localeChanged) { - MethodInfo onChangedHandler = typeof(OptionsMainPanel).GetMethod("OnLocaleChanged", BindingFlags.Instance | BindingFlags.NonPublic); - if (onChangedHandler != null) { - onChangedHandler.Invoke(UIView.library.Get("OptionsPanel"), new object[0] { }); - } - } - } - - private static void onLockButtonChanged(bool newValue) { - Log._Debug($"Button lock changed to {newValue}"); - LoadingExtension.BaseUI.MainMenuButton.SetPosLock(newValue); - GlobalConfig.Instance.Main.MainMenuButtonPosLocked = newValue; - GlobalConfig.WriteConfig(); - } - - private static void onLockMenuChanged(bool newValue) { - Log._Debug($"Menu lock changed to {newValue}"); - LoadingExtension.BaseUI.MainMenu.SetPosLock(newValue); - GlobalConfig.Instance.Main.MainMenuPosLocked = newValue; - GlobalConfig.WriteConfig(); - } - - private static void onTinyMenuChanged(bool newValue) { - Log._Debug($"Menu tiny changed to {newValue}"); - GlobalConfig.Instance.Main.TinyMainMenu = newValue; - GlobalConfig.Instance.NotifyObservers(GlobalConfig.Instance); - GlobalConfig.WriteConfig(); - } - - private static void onEnableTutorialsChanged(bool newValue) { - Log._Debug($"Enable tutorial messages changed to {newValue}"); - GlobalConfig.Instance.Main.EnableTutorial = newValue; - GlobalConfig.WriteConfig(); - } - - private static void onShowCompatibilityCheckErrorChanged(bool newValue) { - Log._Debug($"Show mod compatibility error changed to {newValue}"); - GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage = newValue; - GlobalConfig.WriteConfig(); - } + if (localeChanged) { + MethodInfo onChangedHandler = typeof(OptionsMainPanel).GetMethod("OnLocaleChanged", BindingFlags.Instance | BindingFlags.NonPublic); + if (onChangedHandler != null) { + onChangedHandler.Invoke(UIView.library.Get("OptionsPanel"), new object[0] { }); + } + } + } + + private static void onLockButtonChanged(bool newValue) { + Log._Debug($"Button lock changed to {newValue}"); + LoadingExtension.BaseUI.MainMenuButton.SetPosLock(newValue); + GlobalConfig.Instance.Main.MainMenuButtonPosLocked = newValue; + GlobalConfig.WriteConfig(); + } + + private static void onLockMenuChanged(bool newValue) { + Log._Debug($"Menu lock changed to {newValue}"); + LoadingExtension.BaseUI.MainMenu.SetPosLock(newValue); + GlobalConfig.Instance.Main.MainMenuPosLocked = newValue; + GlobalConfig.WriteConfig(); + } + + private static void onTinyMenuChanged(bool newValue) { + Log._Debug($"Menu tiny changed to {newValue}"); + GlobalConfig.Instance.Main.TinyMainMenu = newValue; + GlobalConfig.Instance.NotifyObservers(GlobalConfig.Instance); + GlobalConfig.WriteConfig(); + } + + private static void onEnableTutorialsChanged(bool newValue) { + Log._Debug($"Enable tutorial messages changed to {newValue}"); + GlobalConfig.Instance.Main.EnableTutorial = newValue; + GlobalConfig.WriteConfig(); + } + + private static void onShowCompatibilityCheckErrorChanged(bool newValue) { + Log._Debug($"Show mod compatibility error changed to {newValue}"); + GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage = newValue; + GlobalConfig.WriteConfig(); + } private static void onScanForKnownIncompatibleModsChanged(bool newValue) { Log._Debug($"Show incompatible mod checker warnings changed to {newValue}"); @@ -743,836 +738,836 @@ private static void onIgnoreDisabledModsChanged(bool newValue) { } private static void onDisplayMphChanged(bool newValue) { - Log._Debug($"Display MPH changed to {newValue}"); - GlobalConfig.Instance.Main.DisplaySpeedLimitsMph = newValue; - GlobalConfig.WriteConfig(); + Log._Debug($"Display MPH changed to {newValue}"); + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph = newValue; + GlobalConfig.WriteConfig(); } public static void setDisplayInMPH(bool value) { - if (displayMphToggle != null) { - displayMphToggle.isChecked = value; - } + if (displayMphToggle != null) { + displayMphToggle.isChecked = value; + } } - private static void onRoadSignsMphThemeChanged(int newRoadSignStyle) { - if (!checkGameLoaded()) { - return; - } + private static void onRoadSignsMphThemeChanged(int newRoadSignStyle) { + if (!checkGameLoaded()) { + return; + } - // The UI order is: US, UK, German - var newStyle = MphSignStyle.RoundGerman; - switch (newRoadSignStyle) { - case 1: - newStyle = MphSignStyle.RoundUK; - break; - case 0: - newStyle = MphSignStyle.SquareUS; - break; - } + // The UI order is: US, UK, German + var newStyle = MphSignStyle.RoundGerman; + switch (newRoadSignStyle) { + case 1: + newStyle = MphSignStyle.RoundUK; + break; + case 0: + newStyle = MphSignStyle.SquareUS; + break; + } - Log._Debug($"Road Sign theme changed to {newStyle}"); - GlobalConfig.Instance.Main.MphRoadSignStyle = newStyle; + Log._Debug($"Road Sign theme changed to {newStyle}"); + GlobalConfig.Instance.Main.MphRoadSignStyle = newStyle; } - private static void onInstantEffectsChanged(bool newValue) { - if (!checkGameLoaded()) - return; + private static void onInstantEffectsChanged(bool newValue) { + if (!checkGameLoaded()) + return; - Log._Debug($"Instant effects changed to {newValue}"); - instantEffects = newValue; - } + Log._Debug($"Instant effects changed to {newValue}"); + instantEffects = newValue; + } - private static void onSimAccuracyChanged(int newAccuracy) { - if (!checkGameLoaded()) - return; + private static void onSimAccuracyChanged(int newAccuracy) { + if (!checkGameLoaded()) + return; - Log._Debug($"Simulation accuracy changed to {newAccuracy}"); - simAccuracy = newAccuracy; - } + Log._Debug($"Simulation accuracy changed to {newAccuracy}"); + simAccuracy = newAccuracy; + } - private static void onVehicleRestrictionsAggressionChanged(int newValue) { - if (!checkGameLoaded()) - return; + private static void onVehicleRestrictionsAggressionChanged(int newValue) { + if (!checkGameLoaded()) + return; - Log._Debug($"vehicleRestrictionsAggression changed to {newValue}"); - setVehicleRestrictionsAggression((VehicleRestrictionsAggression)newValue); - } + Log._Debug($"vehicleRestrictionsAggression changed to {newValue}"); + setVehicleRestrictionsAggression((VehicleRestrictionsAggression)newValue); + } - /*private static void onLaneChangingRandomizationChanged(int newLaneChangingRandomization) { - if (!checkGameLoaded()) - return; + /*private static void onLaneChangingRandomizationChanged(int newLaneChangingRandomization) { + if (!checkGameLoaded()) + return; - Log._Debug($"Lane changing frequency changed to {newLaneChangingRandomization}"); - laneChangingRandomization = newLaneChangingRandomization; - }*/ + Log._Debug($"Lane changing frequency changed to {newLaneChangingRandomization}"); + laneChangingRandomization = newLaneChangingRandomization; + }*/ - private static void onRecklessDriversChanged(int newRecklessDrivers) { - if (!checkGameLoaded()) - return; + private static void onRecklessDriversChanged(int newRecklessDrivers) { + if (!checkGameLoaded()) + return; - Log._Debug($"Reckless driver amount changed to {newRecklessDrivers}"); - recklessDrivers = newRecklessDrivers; - } + Log._Debug($"Reckless driver amount changed to {newRecklessDrivers}"); + recklessDrivers = newRecklessDrivers; + } - private static void onRelaxedBussesChanged(bool newRelaxedBusses) { - if (!checkGameLoaded()) - return; + private static void onRelaxedBussesChanged(bool newRelaxedBusses) { + if (!checkGameLoaded()) + return; - Log._Debug($"Relaxed busses changed to {newRelaxedBusses}"); - relaxedBusses = newRelaxedBusses; - } + Log._Debug($"Relaxed busses changed to {newRelaxedBusses}"); + relaxedBusses = newRelaxedBusses; + } - private static void onAllRelaxedChanged(bool newAllRelaxed) { - if (!checkGameLoaded()) - return; + private static void onAllRelaxedChanged(bool newAllRelaxed) { + if (!checkGameLoaded()) + return; - Log._Debug($"All relaxed changed to {newAllRelaxed}"); - allRelaxed = newAllRelaxed; - } + Log._Debug($"All relaxed changed to {newAllRelaxed}"); + allRelaxed = newAllRelaxed; + } - private static void onAdvancedAIChanged(bool newAdvancedAI) { - if (!checkGameLoaded()) - return; + private static void onAdvancedAIChanged(bool newAdvancedAI) { + if (!checkGameLoaded()) + return; - Log._Debug($"advancedAI changed to {newAdvancedAI}"); - setAdvancedAI(newAdvancedAI); - } + Log._Debug($"advancedAI changed to {newAdvancedAI}"); + setAdvancedAI(newAdvancedAI); + } - private static void onHighwayRulesChanged(bool newHighwayRules) { - if (!checkGameLoaded()) - return; + private static void onHighwayRulesChanged(bool newHighwayRules) { + if (!checkGameLoaded()) + return; - bool changed = newHighwayRules != highwayRules; - if (!changed) { - return; - } + bool changed = newHighwayRules != highwayRules; + if (!changed) { + return; + } - Log._Debug($"Highway rules changed to {newHighwayRules}"); - highwayRules = newHighwayRules; - Flags.clearHighwayLaneArrows(); - Flags.applyAllFlags(); - RoutingManager.Instance.RequestFullRecalculation(); - } - - private static void onPreferOuterLaneChanged(bool val) { - if (!checkGameLoaded()) - return; - - preferOuterLane = val; - } - - private static void onPrioritySignsEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; - - MenuRebuildRequired = true; - prioritySignsEnabled = val; - if (!val) { - setPrioritySignsOverlay(false); - setTrafficLightPriorityRules(false); - } - } + Log._Debug($"Highway rules changed to {newHighwayRules}"); + highwayRules = newHighwayRules; + Flags.clearHighwayLaneArrows(); + Flags.applyAllFlags(); + RoutingManager.Instance.RequestFullRecalculation(); + } - private static void onTimedLightsEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; + private static void onPreferOuterLaneChanged(bool val) { + if (!checkGameLoaded()) + return; - MenuRebuildRequired = true; - timedLightsEnabled = val; - if (!val) { - setTimedLightsOverlay(false); - setTrafficLightPriorityRules(false); - } - } - - private static void onCustomSpeedLimitsEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; - - MenuRebuildRequired = true; - customSpeedLimitsEnabled = val; - if (!val) - setSpeedLimitsOverlay(false); - } - - private static void onVehicleRestrictionsEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; - - MenuRebuildRequired = true; - vehicleRestrictionsEnabled = val; - if (!val) - setVehicleRestrictionsOverlay(false); - } - - private static void onParkingRestrictionsEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; - - MenuRebuildRequired = true; - parkingRestrictionsEnabled = val; - if (!val) - setParkingRestrictionsOverlay(false); - } - - private static void onJunctionRestrictionsEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; - - MenuRebuildRequired = true; - junctionRestrictionsEnabled = val; - if (!val) { - setAllowUTurns(false); - setAllowEnterBlockedJunctions(false); - setAllowLaneChangesWhileGoingStraight(false); - setTurnOnRedEnabled(false); - setJunctionRestrictionsOverlay(false); - } - } + preferOuterLane = val; + } - private static void onTurnOnRedEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; + private static void onPrioritySignsEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; - setTurnOnRedEnabled(val); - } + MenuRebuildRequired = true; + prioritySignsEnabled = val; + if (!val) { + setPrioritySignsOverlay(false); + setTrafficLightPriorityRules(false); + } + } - private static void onLaneConnectorEnabledChanged(bool val) { - if (!checkGameLoaded()) - return; + private static void onTimedLightsEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; - bool changed = val != laneConnectorEnabled; - if (!changed) { - return; - } + MenuRebuildRequired = true; + timedLightsEnabled = val; + if (!val) { + setTimedLightsOverlay(false); + setTrafficLightPriorityRules(false); + } + } - MenuRebuildRequired = true; - laneConnectorEnabled = val; - RoutingManager.Instance.RequestFullRecalculation(); - if (!val) - setConnectedLanesOverlay(false); - } - - private static void onEvacBussesMayIgnoreRulesChanged(bool value) { - if (!checkGameLoaded()) - return; - - Log._Debug($"evacBussesMayIgnoreRules changed to {value}"); - evacBussesMayIgnoreRules = value; - } - - private static void onAllowEnterBlockedJunctionsChanged(bool newValue) { - if (!checkGameLoaded()) - return; - if (newValue && !junctionRestrictionsEnabled) { - setAllowEnterBlockedJunctions(false); - return; - } + private static void onCustomSpeedLimitsEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; - Log._Debug($"allowEnterBlockedJunctions changed to {newValue}"); - setAllowEnterBlockedJunctions(newValue); - } + MenuRebuildRequired = true; + customSpeedLimitsEnabled = val; + if (!val) + setSpeedLimitsOverlay(false); + } - private static void onAllowUTurnsChanged(bool newValue) { - if (!checkGameLoaded()) - return; - if (newValue && !junctionRestrictionsEnabled) { - setAllowUTurns(false); - return; - } + private static void onVehicleRestrictionsEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; - Log._Debug($"allowUTurns changed to {newValue}"); - setAllowUTurns(newValue); - } - - private static void onAllowNearTurnOnRedChanged(bool newValue) { - if (!checkGameLoaded()) - return; - if (newValue && !turnOnRedEnabled) { - setAllowNearTurnOnRed(false); - setAllowFarTurnOnRed(false); - return; - } + MenuRebuildRequired = true; + vehicleRestrictionsEnabled = val; + if (!val) + setVehicleRestrictionsOverlay(false); + } - Log._Debug($"allowNearTurnOnRed changed to {newValue}"); - setAllowNearTurnOnRed(newValue); + private static void onParkingRestrictionsEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; - if (! newValue) { - setAllowFarTurnOnRed(false); - } - } - - private static void onAllowFarTurnOnRedChanged(bool newValue) { - if (!checkGameLoaded()) - return; - if (newValue && (!turnOnRedEnabled || !allowNearTurnOnRed)) { - setAllowFarTurnOnRed(false); - return; - } + MenuRebuildRequired = true; + parkingRestrictionsEnabled = val; + if (!val) + setParkingRestrictionsOverlay(false); + } - Log._Debug($"allowFarTurnOnRed changed to {newValue}"); - setAllowFarTurnOnRed(newValue); - } + private static void onJunctionRestrictionsEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; + + MenuRebuildRequired = true; + junctionRestrictionsEnabled = val; + if (!val) { + setAllowUTurns(false); + setAllowEnterBlockedJunctions(false); + setAllowLaneChangesWhileGoingStraight(false); + setTurnOnRedEnabled(false); + setJunctionRestrictionsOverlay(false); + } + } - private static void onAllowLaneChangesWhileGoingStraightChanged(bool newValue) { - if (!checkGameLoaded()) - return; - if (newValue && !junctionRestrictionsEnabled) { - setAllowLaneChangesWhileGoingStraight(false); - return; - } + private static void onTurnOnRedEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; - Log._Debug($"allowLaneChangesWhileGoingStraight changed to {newValue}"); - setAllowLaneChangesWhileGoingStraight(newValue); - } + setTurnOnRedEnabled(val); + } - private static void onTrafficLightPriorityRulesChanged(bool newValue) { - if (!checkGameLoaded()) - return; - if (newValue && !prioritySignsEnabled) { - setTrafficLightPriorityRules(false); - return; - } + private static void onLaneConnectorEnabledChanged(bool val) { + if (!checkGameLoaded()) + return; - Log._Debug($"trafficLightPriorityRules changed to {newValue}"); - trafficLightPriorityRules = newValue; - if (newValue) { - setPrioritySignsEnabled(true); - setTimedLightsEnabled(true); - } - } + bool changed = val != laneConnectorEnabled; + if (!changed) { + return; + } - private static void onBanRegularTrafficOnBusLanesChanged(bool newValue) { - if (!checkGameLoaded()) - return; + MenuRebuildRequired = true; + laneConnectorEnabled = val; + RoutingManager.Instance.RequestFullRecalculation(); + if (!val) + setConnectedLanesOverlay(false); + } - Log._Debug($"banRegularTrafficOnBusLanes changed to {newValue}"); - banRegularTrafficOnBusLanes = newValue; - VehicleRestrictionsManager.Instance.ClearCache(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + private static void onEvacBussesMayIgnoreRulesChanged(bool value) { + if (!checkGameLoaded()) + return; - private static void onStrongerRoadConditionEffectsChanged(bool newStrongerRoadConditionEffects) { - if (!checkGameLoaded()) - return; + Log._Debug($"evacBussesMayIgnoreRules changed to {value}"); + evacBussesMayIgnoreRules = value; + } - Log._Debug($"strongerRoadConditionEffects changed to {newStrongerRoadConditionEffects}"); - strongerRoadConditionEffects = newStrongerRoadConditionEffects; - } + private static void onAllowEnterBlockedJunctionsChanged(bool newValue) { + if (!checkGameLoaded()) + return; + if (newValue && !junctionRestrictionsEnabled) { + setAllowEnterBlockedJunctions(false); + return; + } - private static void onProhibitPocketCarsChanged(bool newValue) { - if (!checkGameLoaded()) - return; + Log._Debug($"allowEnterBlockedJunctions changed to {newValue}"); + setAllowEnterBlockedJunctions(newValue); + } - Log._Debug($"prohibitPocketCars changed to {newValue}"); + private static void onAllowUTurnsChanged(bool newValue) { + if (!checkGameLoaded()) + return; + if (newValue && !junctionRestrictionsEnabled) { + setAllowUTurns(false); + return; + } - prohibitPocketCars = newValue; - if (prohibitPocketCars) { - AdvancedParkingManager.Instance.OnEnableFeature(); - } else { - AdvancedParkingManager.Instance.OnDisableFeature(); - } - } + Log._Debug($"allowUTurns changed to {newValue}"); + setAllowUTurns(newValue); + } - private static void onRealisticPublicTransportChanged(bool newValue) { - if (!checkGameLoaded()) - return; + private static void onAllowNearTurnOnRedChanged(bool newValue) { + if (!checkGameLoaded()) + return; + if (newValue && !turnOnRedEnabled) { + setAllowNearTurnOnRed(false); + setAllowFarTurnOnRed(false); + return; + } - Log._Debug($"realisticPublicTransport changed to {newValue}"); - realisticPublicTransport = newValue; - } + Log._Debug($"allowNearTurnOnRed changed to {newValue}"); + setAllowNearTurnOnRed(newValue); - private static void onIndividualDrivingStyleChanged(bool value) { - if (!checkGameLoaded()) - return; + if (! newValue) { + setAllowFarTurnOnRed(false); + } + } - Log._Debug($"individualDrivingStyle changed to {value}"); - setIndividualDrivingStyle(value); - } + private static void onAllowFarTurnOnRedChanged(bool newValue) { + if (!checkGameLoaded()) + return; + if (newValue && (!turnOnRedEnabled || !allowNearTurnOnRed)) { + setAllowFarTurnOnRed(false); + return; + } - private static void onDisableDespawningChanged(bool value) { - if (!checkGameLoaded()) - return; + Log._Debug($"allowFarTurnOnRed changed to {newValue}"); + setAllowFarTurnOnRed(newValue); + } - Log._Debug($"disableDespawning changed to {value}"); - disableDespawning = value; - } + private static void onAllowLaneChangesWhileGoingStraightChanged(bool newValue) { + if (!checkGameLoaded()) + return; + if (newValue && !junctionRestrictionsEnabled) { + setAllowLaneChangesWhileGoingStraight(false); + return; + } - private static void onNodesOverlayChanged(bool newNodesOverlay) { - if (!checkGameLoaded()) - return; + Log._Debug($"allowLaneChangesWhileGoingStraight changed to {newValue}"); + setAllowLaneChangesWhileGoingStraight(newValue); + } - Log._Debug($"Nodes overlay changed to {newNodesOverlay}"); - nodesOverlay = newNodesOverlay; - } + private static void onTrafficLightPriorityRulesChanged(bool newValue) { + if (!checkGameLoaded()) + return; + if (newValue && !prioritySignsEnabled) { + setTrafficLightPriorityRules(false); + return; + } - private static void onShowLanesChanged(bool newShowLanes) { - if (!checkGameLoaded()) - return; + Log._Debug($"trafficLightPriorityRules changed to {newValue}"); + trafficLightPriorityRules = newValue; + if (newValue) { + setPrioritySignsEnabled(true); + setTimedLightsEnabled(true); + } + } - Log._Debug($"Show lanes changed to {newShowLanes}"); - showLanes = newShowLanes; - } + private static void onBanRegularTrafficOnBusLanesChanged(bool newValue) { + if (!checkGameLoaded()) + return; - private static void onVehicleOverlayChanged(bool newVal) { - if (!checkGameLoaded()) - return; + Log._Debug($"banRegularTrafficOnBusLanes changed to {newValue}"); + banRegularTrafficOnBusLanes = newValue; + VehicleRestrictionsManager.Instance.ClearCache(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - Log._Debug($"Vehicle overlay changed to {newVal}"); - vehicleOverlay = newVal; - } + private static void onStrongerRoadConditionEffectsChanged(bool newStrongerRoadConditionEffects) { + if (!checkGameLoaded()) + return; - private static void onCitizenOverlayChanged(bool newVal) { - if (!checkGameLoaded()) - return; + Log._Debug($"strongerRoadConditionEffects changed to {newStrongerRoadConditionEffects}"); + strongerRoadConditionEffects = newStrongerRoadConditionEffects; + } - Log._Debug($"Citizen overlay changed to {newVal}"); - citizenOverlay = newVal; - } + private static void onProhibitPocketCarsChanged(bool newValue) { + if (!checkGameLoaded()) + return; - private static void onBuildingOverlayChanged(bool newVal) { - if (!checkGameLoaded()) - return; + Log._Debug($"prohibitPocketCars changed to {newValue}"); - Log._Debug($"Building overlay changed to {newVal}"); - buildingOverlay = newVal; - } + prohibitPocketCars = newValue; + if (prohibitPocketCars) { + AdvancedParkingManager.Instance.OnEnableFeature(); + } else { + AdvancedParkingManager.Instance.OnDisableFeature(); + } + } + + private static void onRealisticPublicTransportChanged(bool newValue) { + if (!checkGameLoaded()) + return; + + Log._Debug($"realisticPublicTransport changed to {newValue}"); + realisticPublicTransport = newValue; + } + + private static void onIndividualDrivingStyleChanged(bool value) { + if (!checkGameLoaded()) + return; + + Log._Debug($"individualDrivingStyle changed to {value}"); + setIndividualDrivingStyle(value); + } + + private static void onDisableDespawningChanged(bool value) { + if (!checkGameLoaded()) + return; + + Log._Debug($"disableDespawning changed to {value}"); + disableDespawning = value; + } + + private static void onNodesOverlayChanged(bool newNodesOverlay) { + if (!checkGameLoaded()) + return; + + Log._Debug($"Nodes overlay changed to {newNodesOverlay}"); + nodesOverlay = newNodesOverlay; + } + + private static void onShowLanesChanged(bool newShowLanes) { + if (!checkGameLoaded()) + return; + + Log._Debug($"Show lanes changed to {newShowLanes}"); + showLanes = newShowLanes; + } + + private static void onVehicleOverlayChanged(bool newVal) { + if (!checkGameLoaded()) + return; + + Log._Debug($"Vehicle overlay changed to {newVal}"); + vehicleOverlay = newVal; + } + + private static void onCitizenOverlayChanged(bool newVal) { + if (!checkGameLoaded()) + return; + + Log._Debug($"Citizen overlay changed to {newVal}"); + citizenOverlay = newVal; + } + + private static void onBuildingOverlayChanged(bool newVal) { + if (!checkGameLoaded()) + return; + + Log._Debug($"Building overlay changed to {newVal}"); + buildingOverlay = newVal; + } #if QUEUEDSTATS - private static void onShowPathFindStatsChanged(bool newVal) { - if (!checkGameLoaded()) - return; + private static void onShowPathFindStatsChanged(bool newVal) { + if (!checkGameLoaded()) + return; - Log._Debug($"Show path-find stats changed to {newVal}"); - showPathFindStats = newVal; - } + Log._Debug($"Show path-find stats changed to {newVal}"); + showPathFindStats = newVal; + } #endif - private static void onFloatValueChanged(string varName, string newValueStr, ref float var) { - if (!checkGameLoaded()) - return; - - try { - float newValue = Single.Parse(newValueStr); - var = newValue; - Log._Debug($"{varName} changed to {newValue}"); - } catch (Exception e) { - Log.Warning($"An invalid value was inserted: '{newValueStr}'. Error: {e.ToString()}"); - //UIView.library.ShowModal("ExceptionPanel").SetMessage("Invalid value", "An invalid value was inserted.", false); - } - } - - private static void onBoolValueChanged(string varName, bool newVal, ref bool var) { - if (!checkGameLoaded()) - return; - - var = newVal; - Log._Debug($"{varName} changed to {newVal}"); - } - - private static void onClickResetStuckEntities() { - if (!checkGameLoaded()) - return; - - Constants.ServiceFactory.SimulationService.AddAction(() => { - UtilityManager.Instance.ResetStuckEntities(); - }); - } - - private static void onClickRemoveParkedVehicles() { - if (!checkGameLoaded()) - return; - - Constants.ServiceFactory.SimulationService.AddAction(() => { - UtilityManager.Instance.RemoveParkedVehicles(); - }); - } - - private static void onClickResetSpeedLimits() { - if (!checkGameLoaded()) - return; - - Flags.resetSpeedLimits(); - } - - private static void onClickReloadGlobalConf() { - GlobalConfig.Reload(); - } - - private static void onClickResetGlobalConf() { - GlobalConfig.Reset(null, true); - } - - public static void setSimAccuracy(int newAccuracy) { - simAccuracy = newAccuracy; - if (simAccuracyDropdown != null) - simAccuracyDropdown.selectedIndex = newAccuracy; - } - - public static void setVehicleRestrictionsAggression(VehicleRestrictionsAggression val) { - bool changed = vehicleRestrictionsAggression != val; - vehicleRestrictionsAggression = val; - if (changed && vehicleRestrictionsAggressionDropdown != null) { - vehicleRestrictionsAggressionDropdown.selectedIndex = (int)val; - } - } + private static void onFloatValueChanged(string varName, string newValueStr, ref float var) { + if (!checkGameLoaded()) + return; + + try { + float newValue = Single.Parse(newValueStr); + var = newValue; + Log._Debug($"{varName} changed to {newValue}"); + } catch (Exception e) { + Log.Warning($"An invalid value was inserted: '{newValueStr}'. Error: {e.ToString()}"); + //UIView.library.ShowModal("ExceptionPanel").SetMessage("Invalid value", "An invalid value was inserted.", false); + } + } - /*public static void setLaneChangingRandomization(int newLaneChangingRandomization) { - laneChangingRandomization = newLaneChangingRandomization; - if (laneChangingRandomizationDropdown != null) - laneChangingRandomizationDropdown.selectedIndex = newLaneChangingRandomization; - }*/ + private static void onBoolValueChanged(string varName, bool newVal, ref bool var) { + if (!checkGameLoaded()) + return; - public static void setRecklessDrivers(int newRecklessDrivers) { - recklessDrivers = newRecklessDrivers; - if (recklessDriversDropdown != null) - recklessDriversDropdown.selectedIndex = newRecklessDrivers; - } - - internal static bool isStockLaneChangerUsed() { - return !advancedAI; - } - - public static void setRelaxedBusses(bool newRelaxedBusses) { - relaxedBusses = newRelaxedBusses; - if (relaxedBussesToggle != null) - relaxedBussesToggle.isChecked = newRelaxedBusses; - } - - public static void setAllRelaxed(bool newAllRelaxed) { - allRelaxed = newAllRelaxed; - if (allRelaxedToggle != null) - allRelaxedToggle.isChecked = newAllRelaxed; - } - - public static void setHighwayRules(bool newHighwayRules) { - highwayRules = newHighwayRules; - - if (highwayRulesToggle != null) - highwayRulesToggle.isChecked = highwayRules; - } - - public static void setPreferOuterLane(bool val) { - preferOuterLane = val; - - if (preferOuterLaneToggle != null) - preferOuterLaneToggle.isChecked = preferOuterLane; - } - - public static void setShowLanes(bool newShowLanes) { - showLanes = newShowLanes; - if (showLanesToggle != null) - showLanesToggle.isChecked = newShowLanes; - } - - public static void setAdvancedAI(bool newAdvancedAI) { - bool changed = newAdvancedAI != advancedAI; - advancedAI = newAdvancedAI; - - if (changed && advancedAIToggle != null) { - advancedAIToggle.isChecked = newAdvancedAI; - } + var = newVal; + Log._Debug($"{varName} changed to {newVal}"); + } - if (changed && !newAdvancedAI) { - setAltLaneSelectionRatio(0); - } - } + private static void onClickResetStuckEntities() { + if (!checkGameLoaded()) + return; - public static void setGuiTransparency(byte val) { - bool changed = val != GlobalConfig.Instance.Main.GuiTransparency; - GlobalConfig.Instance.Main.GuiTransparency = val; + Constants.ServiceFactory.SimulationService.AddAction(() => { + UtilityManager.Instance.ResetStuckEntities(); + }); + } - if (changed && guiTransparencySlider != null) { - guiTransparencySlider.value = val; - } - } + private static void onClickRemoveParkedVehicles() { + if (!checkGameLoaded()) + return; - public static void setOverlayTransparency(byte val) { - bool changed = val != GlobalConfig.Instance.Main.OverlayTransparency; - GlobalConfig.Instance.Main.OverlayTransparency = val; + Constants.ServiceFactory.SimulationService.AddAction(() => { + UtilityManager.Instance.RemoveParkedVehicles(); + }); + } - if (changed && overlayTransparencySlider != null) { - overlayTransparencySlider.value = val; - } - } + private static void onClickResetSpeedLimits() { + if (!checkGameLoaded()) + return; - public static void setAltLaneSelectionRatio(byte val) { - bool changed = val != altLaneSelectionRatio; - altLaneSelectionRatio = val; + Flags.resetSpeedLimits(); + } - if (changed && altLaneSelectionRatioSlider != null) { - altLaneSelectionRatioSlider.value = val; - } + private static void onClickReloadGlobalConf() { + GlobalConfig.Reload(); + } - if (changed && altLaneSelectionRatio > 0) { - setAdvancedAI(true); - } - } - - public static void setEvacBussesMayIgnoreRules(bool value) { - if (! SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) - value = false; - - evacBussesMayIgnoreRules = value; - if (evacBussesMayIgnoreRulesToggle != null) - evacBussesMayIgnoreRulesToggle.isChecked = value; - } - - public static void setInstantEffects(bool value) { - instantEffects = value; - if (instantEffectsToggle != null) - instantEffectsToggle.isChecked = value; - } - - public static void setMayEnterBlockedJunctions(bool newMayEnterBlockedJunctions) { - allowEnterBlockedJunctions = newMayEnterBlockedJunctions; - if (allowEnterBlockedJunctionsToggle != null) - allowEnterBlockedJunctionsToggle.isChecked = newMayEnterBlockedJunctions; - } - - public static void setStrongerRoadConditionEffects(bool newStrongerRoadConditionEffects) { - if (!SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { - newStrongerRoadConditionEffects = false; - } + private static void onClickResetGlobalConf() { + GlobalConfig.Reset(null, true); + } - strongerRoadConditionEffects = newStrongerRoadConditionEffects; - if (strongerRoadConditionEffectsToggle != null) - strongerRoadConditionEffectsToggle.isChecked = newStrongerRoadConditionEffects; - } - - public static void setProhibitPocketCars(bool newValue) { - bool valueChanged = newValue != prohibitPocketCars; - prohibitPocketCars = newValue; - if (prohibitPocketCarsToggle != null) - prohibitPocketCarsToggle.isChecked = newValue; - } - - public static void setRealisticPublicTransport(bool newValue) { - bool valueChanged = newValue != realisticPublicTransport; - realisticPublicTransport = newValue; - if (realisticPublicTransportToggle != null) - realisticPublicTransportToggle.isChecked = newValue; - } - - public static void setIndividualDrivingStyle(bool newValue) { - individualDrivingStyle = newValue; - - if (individualDrivingStyleToggle != null) { - individualDrivingStyleToggle.isChecked = newValue; - } - } - - public static void setDisableDespawning(bool value) { - disableDespawning = value; - - if (disableDespawningToggle != null) - disableDespawningToggle.isChecked = value; - } - - public static void setAllowUTurns(bool value) { - allowUTurns = value; - if (allowUTurnsToggle != null) - allowUTurnsToggle.isChecked = value; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setAllowNearTurnOnRed(bool newValue) { - allowNearTurnOnRed = newValue; - if (allowNearTurnOnRedToggle != null) - allowNearTurnOnRedToggle.isChecked = newValue; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setAllowFarTurnOnRed(bool newValue) { - allowFarTurnOnRed = newValue; - if (allowFarTurnOnRedToggle != null) - allowFarTurnOnRedToggle.isChecked = newValue; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setAllowLaneChangesWhileGoingStraight(bool value) { - allowLaneChangesWhileGoingStraight = value; - if (allowLaneChangesWhileGoingStraightToggle != null) - allowLaneChangesWhileGoingStraightToggle.isChecked = value; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setAllowEnterBlockedJunctions(bool value) { - allowEnterBlockedJunctions = value; - if (allowEnterBlockedJunctionsToggle != null) - allowEnterBlockedJunctionsToggle.isChecked = value; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setTrafficLightPriorityRules(bool value) { - trafficLightPriorityRules = value; - if (trafficLightPriorityRulesToggle != null) - trafficLightPriorityRulesToggle.isChecked = value; - } - - public static void setBanRegularTrafficOnBusLanes(bool value) { - banRegularTrafficOnBusLanes = value; - if (banRegularTrafficOnBusLanesToggle != null) - banRegularTrafficOnBusLanesToggle.isChecked = value; - - VehicleRestrictionsManager.Instance.ClearCache(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setPrioritySignsOverlay(bool newPrioritySignsOverlay) { - prioritySignsOverlay = newPrioritySignsOverlay; - if (prioritySignsOverlayToggle != null) - prioritySignsOverlayToggle.isChecked = newPrioritySignsOverlay; - - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setTimedLightsOverlay(bool newTimedLightsOverlay) { - timedLightsOverlay = newTimedLightsOverlay; - if (timedLightsOverlayToggle != null) - timedLightsOverlayToggle.isChecked = newTimedLightsOverlay; - - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setSpeedLimitsOverlay(bool newSpeedLimitsOverlay) { - speedLimitsOverlay = newSpeedLimitsOverlay; - if (speedLimitsOverlayToggle != null) - speedLimitsOverlayToggle.isChecked = newSpeedLimitsOverlay; - - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setVehicleRestrictionsOverlay(bool newVehicleRestrictionsOverlay) { - vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; - if (vehicleRestrictionsOverlayToggle != null) - vehicleRestrictionsOverlayToggle.isChecked = newVehicleRestrictionsOverlay; - - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setParkingRestrictionsOverlay(bool newParkingRestrictionsOverlay) { - parkingRestrictionsOverlay = newParkingRestrictionsOverlay; - if (parkingRestrictionsOverlayToggle != null) - parkingRestrictionsOverlayToggle.isChecked = newParkingRestrictionsOverlay; - - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setJunctionRestrictionsOverlay(bool newValue) { - junctionRestrictionsOverlay = newValue; - if (junctionRestrictionsOverlayToggle != null) - junctionRestrictionsOverlayToggle.isChecked = newValue; - - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setConnectedLanesOverlay(bool newValue) { - connectedLanesOverlay = newValue; - if (connectedLanesOverlayToggle != null) - connectedLanesOverlayToggle.isChecked = newValue; - } - - public static void setNodesOverlay(bool newNodesOverlay) { - nodesOverlay = newNodesOverlay; - if (nodesOverlayToggle != null) - nodesOverlayToggle.isChecked = newNodesOverlay; - - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } - - public static void setVehicleOverlay(bool newVal) { - vehicleOverlay = newVal; - if (vehicleOverlayToggle != null) - vehicleOverlayToggle.isChecked = newVal; - } - - public static void setPrioritySignsEnabled(bool newValue) { - MenuRebuildRequired = true; - prioritySignsEnabled = newValue; - if (enablePrioritySignsToggle != null) - enablePrioritySignsToggle.isChecked = newValue; - if (!newValue) - setPrioritySignsOverlay(false); - } - - public static void setTimedLightsEnabled(bool newValue) { - MenuRebuildRequired = true; - timedLightsEnabled = newValue; - if (enableTimedLightsToggle != null) - enableTimedLightsToggle.isChecked = newValue; - if (!newValue) - setTimedLightsOverlay(false); - } - - public static void setCustomSpeedLimitsEnabled(bool newValue) { - MenuRebuildRequired = true; - customSpeedLimitsEnabled = newValue; - if (enableCustomSpeedLimitsToggle != null) - enableCustomSpeedLimitsToggle.isChecked = newValue; - if (!newValue) - setSpeedLimitsOverlay(false); - } - - public static void setVehicleRestrictionsEnabled(bool newValue) { - MenuRebuildRequired = true; - vehicleRestrictionsEnabled = newValue; - if (enableVehicleRestrictionsToggle != null) - enableVehicleRestrictionsToggle.isChecked = newValue; - if (!newValue) - setVehicleRestrictionsOverlay(false); - } - - public static void setParkingRestrictionsEnabled(bool newValue) { - MenuRebuildRequired = true; - parkingRestrictionsEnabled = newValue; - if (enableParkingRestrictionsToggle != null) - enableParkingRestrictionsToggle.isChecked = newValue; - if (!newValue) - setParkingRestrictionsOverlay(false); - } - - public static void setJunctionRestrictionsEnabled(bool newValue) { - MenuRebuildRequired = true; - junctionRestrictionsEnabled = newValue; - if (enableJunctionRestrictionsToggle != null) - enableJunctionRestrictionsToggle.isChecked = newValue; - if (!newValue) - setJunctionRestrictionsOverlay(false); - } - - public static void setTurnOnRedEnabled(bool newValue) { - turnOnRedEnabled = newValue; - if (turnOnRedEnabledToggle != null) - turnOnRedEnabledToggle.isChecked = newValue; - if (!newValue) { - setAllowNearTurnOnRed(false); - setAllowFarTurnOnRed(false); - } - } + public static void setSimAccuracy(int newAccuracy) { + simAccuracy = newAccuracy; + if (simAccuracyDropdown != null) + simAccuracyDropdown.selectedIndex = newAccuracy; + } + + public static void setVehicleRestrictionsAggression(VehicleRestrictionsAggression val) { + bool changed = vehicleRestrictionsAggression != val; + vehicleRestrictionsAggression = val; + if (changed && vehicleRestrictionsAggressionDropdown != null) { + vehicleRestrictionsAggressionDropdown.selectedIndex = (int)val; + } + } + + /*public static void setLaneChangingRandomization(int newLaneChangingRandomization) { + laneChangingRandomization = newLaneChangingRandomization; + if (laneChangingRandomizationDropdown != null) + laneChangingRandomizationDropdown.selectedIndex = newLaneChangingRandomization; + }*/ + + public static void setRecklessDrivers(int newRecklessDrivers) { + recklessDrivers = newRecklessDrivers; + if (recklessDriversDropdown != null) + recklessDriversDropdown.selectedIndex = newRecklessDrivers; + } + + internal static bool isStockLaneChangerUsed() { + return !advancedAI; + } + + public static void setRelaxedBusses(bool newRelaxedBusses) { + relaxedBusses = newRelaxedBusses; + if (relaxedBussesToggle != null) + relaxedBussesToggle.isChecked = newRelaxedBusses; + } + + public static void setAllRelaxed(bool newAllRelaxed) { + allRelaxed = newAllRelaxed; + if (allRelaxedToggle != null) + allRelaxedToggle.isChecked = newAllRelaxed; + } + + public static void setHighwayRules(bool newHighwayRules) { + highwayRules = newHighwayRules; + + if (highwayRulesToggle != null) + highwayRulesToggle.isChecked = highwayRules; + } + + public static void setPreferOuterLane(bool val) { + preferOuterLane = val; + + if (preferOuterLaneToggle != null) + preferOuterLaneToggle.isChecked = preferOuterLane; + } + + public static void setShowLanes(bool newShowLanes) { + showLanes = newShowLanes; + if (showLanesToggle != null) + showLanesToggle.isChecked = newShowLanes; + } + + public static void setAdvancedAI(bool newAdvancedAI) { + bool changed = newAdvancedAI != advancedAI; + advancedAI = newAdvancedAI; + + if (changed && advancedAIToggle != null) { + advancedAIToggle.isChecked = newAdvancedAI; + } + + if (changed && !newAdvancedAI) { + setAltLaneSelectionRatio(0); + } + } + + public static void setGuiTransparency(byte val) { + bool changed = val != GlobalConfig.Instance.Main.GuiTransparency; + GlobalConfig.Instance.Main.GuiTransparency = val; + + if (changed && guiTransparencySlider != null) { + guiTransparencySlider.value = val; + } + } + + public static void setOverlayTransparency(byte val) { + bool changed = val != GlobalConfig.Instance.Main.OverlayTransparency; + GlobalConfig.Instance.Main.OverlayTransparency = val; + + if (changed && overlayTransparencySlider != null) { + overlayTransparencySlider.value = val; + } + } + + public static void setAltLaneSelectionRatio(byte val) { + bool changed = val != altLaneSelectionRatio; + altLaneSelectionRatio = val; + + if (changed && altLaneSelectionRatioSlider != null) { + altLaneSelectionRatioSlider.value = val; + } + + if (changed && altLaneSelectionRatio > 0) { + setAdvancedAI(true); + } + } + + public static void setEvacBussesMayIgnoreRules(bool value) { + if (! SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) + value = false; + + evacBussesMayIgnoreRules = value; + if (evacBussesMayIgnoreRulesToggle != null) + evacBussesMayIgnoreRulesToggle.isChecked = value; + } + + public static void setInstantEffects(bool value) { + instantEffects = value; + if (instantEffectsToggle != null) + instantEffectsToggle.isChecked = value; + } + + public static void setMayEnterBlockedJunctions(bool newMayEnterBlockedJunctions) { + allowEnterBlockedJunctions = newMayEnterBlockedJunctions; + if (allowEnterBlockedJunctionsToggle != null) + allowEnterBlockedJunctionsToggle.isChecked = newMayEnterBlockedJunctions; + } + + public static void setStrongerRoadConditionEffects(bool newStrongerRoadConditionEffects) { + if (!SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { + newStrongerRoadConditionEffects = false; + } + + strongerRoadConditionEffects = newStrongerRoadConditionEffects; + if (strongerRoadConditionEffectsToggle != null) + strongerRoadConditionEffectsToggle.isChecked = newStrongerRoadConditionEffects; + } + + public static void setProhibitPocketCars(bool newValue) { + bool valueChanged = newValue != prohibitPocketCars; + prohibitPocketCars = newValue; + if (prohibitPocketCarsToggle != null) + prohibitPocketCarsToggle.isChecked = newValue; + } + + public static void setRealisticPublicTransport(bool newValue) { + bool valueChanged = newValue != realisticPublicTransport; + realisticPublicTransport = newValue; + if (realisticPublicTransportToggle != null) + realisticPublicTransportToggle.isChecked = newValue; + } + + public static void setIndividualDrivingStyle(bool newValue) { + individualDrivingStyle = newValue; + + if (individualDrivingStyleToggle != null) { + individualDrivingStyleToggle.isChecked = newValue; + } + } + + public static void setDisableDespawning(bool value) { + disableDespawning = value; + + if (disableDespawningToggle != null) + disableDespawningToggle.isChecked = value; + } + + public static void setAllowUTurns(bool value) { + allowUTurns = value; + if (allowUTurnsToggle != null) + allowUTurnsToggle.isChecked = value; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setAllowNearTurnOnRed(bool newValue) { + allowNearTurnOnRed = newValue; + if (allowNearTurnOnRedToggle != null) + allowNearTurnOnRedToggle.isChecked = newValue; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setAllowFarTurnOnRed(bool newValue) { + allowFarTurnOnRed = newValue; + if (allowFarTurnOnRedToggle != null) + allowFarTurnOnRedToggle.isChecked = newValue; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setAllowLaneChangesWhileGoingStraight(bool value) { + allowLaneChangesWhileGoingStraight = value; + if (allowLaneChangesWhileGoingStraightToggle != null) + allowLaneChangesWhileGoingStraightToggle.isChecked = value; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setLaneConnectorEnabled(bool newValue) { - MenuRebuildRequired = true; - laneConnectorEnabled = newValue; - if (enableLaneConnectorToggle != null) - enableLaneConnectorToggle.isChecked = newValue; - if (!newValue) - setConnectedLanesOverlay(false); - } + public static void setAllowEnterBlockedJunctions(bool value) { + allowEnterBlockedJunctions = value; + if (allowEnterBlockedJunctionsToggle != null) + allowEnterBlockedJunctionsToggle.isChecked = value; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setTrafficLightPriorityRules(bool value) { + trafficLightPriorityRules = value; + if (trafficLightPriorityRulesToggle != null) + trafficLightPriorityRulesToggle.isChecked = value; + } + + public static void setBanRegularTrafficOnBusLanes(bool value) { + banRegularTrafficOnBusLanes = value; + if (banRegularTrafficOnBusLanesToggle != null) + banRegularTrafficOnBusLanesToggle.isChecked = value; + + VehicleRestrictionsManager.Instance.ClearCache(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setPrioritySignsOverlay(bool newPrioritySignsOverlay) { + prioritySignsOverlay = newPrioritySignsOverlay; + if (prioritySignsOverlayToggle != null) + prioritySignsOverlayToggle.isChecked = newPrioritySignsOverlay; + + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setTimedLightsOverlay(bool newTimedLightsOverlay) { + timedLightsOverlay = newTimedLightsOverlay; + if (timedLightsOverlayToggle != null) + timedLightsOverlayToggle.isChecked = newTimedLightsOverlay; + + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setSpeedLimitsOverlay(bool newSpeedLimitsOverlay) { + speedLimitsOverlay = newSpeedLimitsOverlay; + if (speedLimitsOverlayToggle != null) + speedLimitsOverlayToggle.isChecked = newSpeedLimitsOverlay; + + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setVehicleRestrictionsOverlay(bool newVehicleRestrictionsOverlay) { + vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; + if (vehicleRestrictionsOverlayToggle != null) + vehicleRestrictionsOverlayToggle.isChecked = newVehicleRestrictionsOverlay; + + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setParkingRestrictionsOverlay(bool newParkingRestrictionsOverlay) { + parkingRestrictionsOverlay = newParkingRestrictionsOverlay; + if (parkingRestrictionsOverlayToggle != null) + parkingRestrictionsOverlayToggle.isChecked = newParkingRestrictionsOverlay; + + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setJunctionRestrictionsOverlay(bool newValue) { + junctionRestrictionsOverlay = newValue; + if (junctionRestrictionsOverlayToggle != null) + junctionRestrictionsOverlayToggle.isChecked = newValue; + + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setConnectedLanesOverlay(bool newValue) { + connectedLanesOverlay = newValue; + if (connectedLanesOverlayToggle != null) + connectedLanesOverlayToggle.isChecked = newValue; + } + + public static void setNodesOverlay(bool newNodesOverlay) { + nodesOverlay = newNodesOverlay; + if (nodesOverlayToggle != null) + nodesOverlayToggle.isChecked = newNodesOverlay; + + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } + + public static void setVehicleOverlay(bool newVal) { + vehicleOverlay = newVal; + if (vehicleOverlayToggle != null) + vehicleOverlayToggle.isChecked = newVal; + } + + public static void setPrioritySignsEnabled(bool newValue) { + MenuRebuildRequired = true; + prioritySignsEnabled = newValue; + if (enablePrioritySignsToggle != null) + enablePrioritySignsToggle.isChecked = newValue; + if (!newValue) + setPrioritySignsOverlay(false); + } + + public static void setTimedLightsEnabled(bool newValue) { + MenuRebuildRequired = true; + timedLightsEnabled = newValue; + if (enableTimedLightsToggle != null) + enableTimedLightsToggle.isChecked = newValue; + if (!newValue) + setTimedLightsOverlay(false); + } + + public static void setCustomSpeedLimitsEnabled(bool newValue) { + MenuRebuildRequired = true; + customSpeedLimitsEnabled = newValue; + if (enableCustomSpeedLimitsToggle != null) + enableCustomSpeedLimitsToggle.isChecked = newValue; + if (!newValue) + setSpeedLimitsOverlay(false); + } + + public static void setVehicleRestrictionsEnabled(bool newValue) { + MenuRebuildRequired = true; + vehicleRestrictionsEnabled = newValue; + if (enableVehicleRestrictionsToggle != null) + enableVehicleRestrictionsToggle.isChecked = newValue; + if (!newValue) + setVehicleRestrictionsOverlay(false); + } + + public static void setParkingRestrictionsEnabled(bool newValue) { + MenuRebuildRequired = true; + parkingRestrictionsEnabled = newValue; + if (enableParkingRestrictionsToggle != null) + enableParkingRestrictionsToggle.isChecked = newValue; + if (!newValue) + setParkingRestrictionsOverlay(false); + } + + public static void setJunctionRestrictionsEnabled(bool newValue) { + MenuRebuildRequired = true; + junctionRestrictionsEnabled = newValue; + if (enableJunctionRestrictionsToggle != null) + enableJunctionRestrictionsToggle.isChecked = newValue; + if (!newValue) + setJunctionRestrictionsOverlay(false); + } + + public static void setTurnOnRedEnabled(bool newValue) { + turnOnRedEnabled = newValue; + if (turnOnRedEnabledToggle != null) + turnOnRedEnabledToggle.isChecked = newValue; + if (!newValue) { + setAllowNearTurnOnRed(false); + setAllowFarTurnOnRed(false); + } + } + + public static void setLaneConnectorEnabled(bool newValue) { + MenuRebuildRequired = true; + laneConnectorEnabled = newValue; + if (enableLaneConnectorToggle != null) + enableLaneConnectorToggle.isChecked = newValue; + if (!newValue) + setConnectedLanesOverlay(false); + } #if QUEUEDSTATS - public static void setShowPathFindStats(bool value) { - showPathFindStats = value; - if (showPathFindStatsToggle != null) - showPathFindStatsToggle.isChecked = value; - } + public static void setShowPathFindStats(bool value) { + showPathFindStats = value; + if (showPathFindStatsToggle != null) + showPathFindStatsToggle.isChecked = value; + } #endif - + public static void setScanForKnownIncompatibleMods(bool value) { scanForKnownIncompatibleModsEnabled = value; if (scanForKnownIncompatibleModsToggle != null) { @@ -1628,25 +1623,25 @@ public static void setIgnoreDisabledMods(bool value) { }*/ internal static int getRecklessDriverModulo() { - switch (recklessDrivers) { - case 0: - return 10; - case 1: - return 20; - case 2: - return 50; - case 3: - return 10000; - } - return 10000; - } - - /// - /// Determines whether Dynamic Lane Selection (DLS) is enabled. - /// - /// - public static bool IsDynamicLaneSelectionActive() { - return advancedAI && altLaneSelectionRatio > 0; - } - } -} + switch (recklessDrivers) { + case 0: + return 10; + case 1: + return 20; + case 2: + return 50; + case 3: + return 10000; + } + return 10000; + } + + /// + /// Determines whether Dynamic Lane Selection (DLS) is enabled. + /// + /// + public static bool IsDynamicLaneSelectionActive() { + return advancedAI && altLaneSelectionRatio > 0; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 0466dcb55..0e5219994 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -176,7 +176,9 @@ - + + + diff --git a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs index 634714780..eb02f9727 100644 --- a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs +++ b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework.UI; using TrafficManager.Manager.Impl; namespace TrafficManager.UI.MainMenu { diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs index ea8735544..19a67cb43 100644 --- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs @@ -1,5 +1,6 @@ using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class JunctionRestrictionsButton : MenuToolModeButton { @@ -7,6 +8,6 @@ public class JunctionRestrictionsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; public override string Tooltip => "Junction_restrictions"; public override bool Visible => Options.junctionRestrictionsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyJunctionRestrictionsTool; + public override SavedInputKey ShortcutKey => KeymappingSettings.KeyJunctionRestrictionsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs index 0e743c08b..41e2a7ddb 100644 --- a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs @@ -1,5 +1,6 @@ using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class LaneArrowsButton : MenuToolModeButton { @@ -7,6 +8,6 @@ public class LaneArrowsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.LaneArrows; public override string Tooltip => "Change_lane_arrows"; public override bool Visible => true; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneArrowTool; + public override SavedInputKey ShortcutKey => KeymappingSettings.KeyLaneArrowTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs index 319e4428b..bb32f5169 100644 --- a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs @@ -1,5 +1,6 @@ using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class LaneConnectorButton : MenuToolModeButton { @@ -7,6 +8,6 @@ public class LaneConnectorButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.LaneConnector; public override string Tooltip => "Lane_connector"; public override bool Visible => Options.laneConnectorEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyLaneConnectionsTool; + public override SavedInputKey ShortcutKey => KeymappingSettings.KeyLaneConnectionsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index 5e31f1f65..56027f809 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using TrafficManager.Manager; using CSUtil.Commons; +using TrafficManager.State.Keybinds; using TrafficManager.UI.SubTools; using TrafficManager.Util; @@ -217,17 +218,17 @@ public void OnGUI() { // Some safety checks to not trigger while full screen/modals are open // Check the key and then click the corresponding button - if (OptionsKeymapping.KeyToggleTrafficLightTool.IsPressed(Event.current)) { + if (KeymappingSettings.KeyToggleTrafficLightTool.IsPressed(Event.current)) { ClickToolButton(typeof(ToggleTrafficLightsButton)); - } else if (OptionsKeymapping.KeyLaneArrowTool.IsPressed(Event.current)) { + } else if (KeymappingSettings.KeyLaneArrowTool.IsPressed(Event.current)) { ClickToolButton(typeof(LaneArrowsButton)); - } else if (OptionsKeymapping.KeyLaneConnectionsTool.IsPressed(Event.current)) { + } else if (KeymappingSettings.KeyLaneConnectionsTool.IsPressed(Event.current)) { ClickToolButton(typeof(LaneConnectorButton)); - } else if (OptionsKeymapping.KeyPrioritySignsTool.IsPressed(Event.current)) { + } else if (KeymappingSettings.KeyPrioritySignsTool.IsPressed(Event.current)) { ClickToolButton(typeof(PrioritySignsButton)); - } else if (OptionsKeymapping.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { + } else if (KeymappingSettings.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { ClickToolButton(typeof(JunctionRestrictionsButton)); - } else if (OptionsKeymapping.KeySpeedLimitsTool.IsPressed(Event.current)) { + } else if (KeymappingSettings.KeySpeedLimitsTool.IsPressed(Event.current)) { ClickToolButton(typeof(SpeedLimitsButton)); } } diff --git a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs index 7e9c587e5..7a49e7f3b 100644 --- a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs +++ b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs @@ -1,5 +1,6 @@ using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class PrioritySignsButton : MenuToolModeButton { @@ -7,6 +8,6 @@ public class PrioritySignsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.PrioritySigns; public override string Tooltip => "Add_priority_signs"; public override bool Visible => Options.prioritySignsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyPrioritySignsTool; + public override SavedInputKey ShortcutKey => KeymappingSettings.KeyPrioritySignsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs index f0787bff4..dc9c34683 100644 --- a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs +++ b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs @@ -1,5 +1,6 @@ using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class SpeedLimitsButton : MenuToolModeButton { @@ -7,6 +8,6 @@ public class SpeedLimitsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.SpeedLimits; public override string Tooltip => "Speed_limits"; public override bool Visible => Options.customSpeedLimitsEnabled; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeySpeedLimitsTool; + public override SavedInputKey ShortcutKey => KeymappingSettings.KeySpeedLimitsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs index c793b0005..8c9e71e3f 100644 --- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs @@ -1,5 +1,6 @@ using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class ToggleTrafficLightsButton : MenuToolModeButton { @@ -7,6 +8,6 @@ public class ToggleTrafficLightsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; public override string Tooltip => "Switch_traffic_lights"; public override bool Visible => true; - public override SavedInputKey ShortcutKey => OptionsKeymapping.KeyToggleTrafficLightTool; + public override SavedInputKey ShortcutKey => KeymappingSettings.KeyToggleTrafficLightTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs index ad1574a90..c5ff91056 100644 --- a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs @@ -5,6 +5,7 @@ public class VehicleRestrictionsButton : MenuToolModeButton { public override ToolMode ToolMode => ToolMode.VehicleRestrictions; public override ButtonFunction Function => ButtonFunction.VehicleRestrictions; public override string Tooltip => "Vehicle_restrictions"; + public override bool Visible => Options.vehicleRestrictionsEnabled; } } \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index 06e58f018..a09931252 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -13,6 +13,7 @@ using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Manager.Impl; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.SubTools { public class LaneConnectorTool : SubTool { @@ -71,13 +72,13 @@ public override void OnToolGUI(Event e) { // $"SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} " + // $"HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); - if (OptionsKeymapping.KeyLaneConnectorStayInLane.IsPressed(e)) { + if (KeymappingSettings.KeyLaneConnectorStayInLane.IsPressed(e)) { frameStayInLanePressed = Time.frameCount; // this will be consumed in RenderOverlay() if the key was pressed // not too long ago (within 20 Unity frames or 0.33 sec) } - if (OptionsKeymapping.KeyLaneConnectorDelete.IsPressed(e)) { + if (KeymappingSettings.KeyLaneConnectorDelete.IsPressed(e)) { frameClearPressed = Time.frameCount; // this will be consumed in RenderOverlay() if the key was pressed // not too long ago (within 20 Unity frames or 0.33 sec) diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index a9ef032c6..2d17c5f80 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using TrafficManager.State; +using TrafficManager.State.Keybinds; using TrafficManager.Util; using UnityEngine; @@ -127,7 +128,7 @@ public void UpdatePosition(Vector2 pos) { public void OnGUI() { if (!UIView.HasModalInput() && !UIView.HasInputFocus() - && OptionsKeymapping.KeyToggleTMPEMainMenu.IsPressed(Event.current)) { + && KeymappingSettings.KeyToggleTMPEMainMenu.IsPressed(Event.current)) { LoadingExtension.BaseUI?.ToggleMainMenu(); } From 0799d5ad07c75c95e373551f92f382a9468b9dfd Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Fri, 28 Jun 2019 23:03:47 +0200 Subject: [PATCH 099/142] Reindent MainMenuPanel --- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 53 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index 56027f809..716c32919 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -49,32 +49,33 @@ public class SizeProfile { public int MENU_HEIGHT { get; set; } } - public static readonly SizeProfile[] SIZE_PROFILES = new SizeProfile[] { - new SizeProfile() { - NUM_BUTTONS_PER_ROW = 6, - NUM_ROWS = 2, - - VSPACING = 5, - HSPACING = 5, - TOP_BORDER = 25, - BUTTON_SIZE = 30, - - MENU_WIDTH = 215, - MENU_HEIGHT = 95 - }, - new SizeProfile() { - NUM_BUTTONS_PER_ROW = 6, - NUM_ROWS = 2, - - VSPACING = 5, - HSPACING = 5, - TOP_BORDER = 25, - BUTTON_SIZE = 50, - - MENU_WIDTH = 335, - MENU_HEIGHT = 135 - } - }; + public static readonly SizeProfile[] SIZE_PROFILES + = { + new SizeProfile() { + NUM_BUTTONS_PER_ROW = 6, + NUM_ROWS = 2, + + VSPACING = 5, + HSPACING = 5, + TOP_BORDER = 25, + BUTTON_SIZE = 30, + + MENU_WIDTH = 215, + MENU_HEIGHT = 95 + }, + new SizeProfile() { + NUM_BUTTONS_PER_ROW = 6, + NUM_ROWS = 2, + + VSPACING = 5, + HSPACING = 5, + TOP_BORDER = 25, + BUTTON_SIZE = 50, + + MENU_WIDTH = 335, + MENU_HEIGHT = 135 + } + }; public const int DEFAULT_MENU_X = 85; public const int DEFAULT_MENU_Y = 60; From a656f21e97ec87d77afcf0ef0b8ab7f5c4d5a8d5 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Fri, 28 Jun 2019 23:08:00 +0200 Subject: [PATCH 100/142] Untabify and remove unused directives --- TLM/TLM/UI/SubTools/LaneConnectorTool.cs | 1165 ++++++------ TLM/TLM/UI/ToolMode.cs | 34 +- TLM/TLM/UI/TrafficManagerTool.cs | 2088 +++++++++++----------- TLM/TLM/UI/UIBase.cs | 315 ++-- 4 files changed, 1793 insertions(+), 1809 deletions(-) diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index a09931252..9c360cc87 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -1,670 +1,663 @@ using ColossalFramework; using ColossalFramework.Math; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using TrafficManager.Custom.AI; using TrafficManager.State; -using TrafficManager.Geometry; -using TrafficManager.TrafficLight; -using TrafficManager.Util; using UnityEngine; -using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Manager.Impl; using TrafficManager.State.Keybinds; namespace TrafficManager.UI.SubTools { - public class LaneConnectorTool : SubTool { - enum MarkerSelectionMode { - None, - SelectSource, - SelectTarget - } - - enum StayInLaneMode { - None, - Both, - Forward, - Backward - } - - private static readonly Color DefaultNodeMarkerColor = new Color(1f, 1f, 1f, 0.4f); - private NodeLaneMarker selectedMarker = null; - private NodeLaneMarker hoveredMarker = null; - private Dictionary> currentNodeMarkers; - private StayInLaneMode stayInLaneMode = StayInLaneMode.None; - //private bool initDone = false; - - /// Unity frame when OnGui detected the shortcut for Stay in Lane. - /// Resets when the event is consumed or after a few frames. - private int frameStayInLanePressed = 0; - - /// Clear lane lines is Delete (configurable) - private int frameClearPressed = 0; - - class NodeLaneMarker { - internal ushort segmentId; - internal ushort nodeId; - internal bool startNode; - internal Vector3 position; - internal Vector3 secondaryPosition; - internal bool isSource; - internal bool isTarget; - internal uint laneId; - internal int innerSimilarLaneIndex; - internal int segmentIndex; - internal float radius = 1f; - internal Color color; - internal List connectedMarkers = new List(); - internal NetInfo.LaneType laneType; - internal VehicleInfo.VehicleType vehicleType; - } - - public LaneConnectorTool(TrafficManagerTool mainTool) : base(mainTool) { - //Log._Debug($"TppLaneConnectorTool: Constructor called"); - currentNodeMarkers = new Dictionary>(); - } - - public override void OnToolGUI(Event e) { + public class LaneConnectorTool : SubTool { + enum MarkerSelectionMode { + None, + SelectSource, + SelectTarget + } + + enum StayInLaneMode { + None, + Both, + Forward, + Backward + } + + private static readonly Color DefaultNodeMarkerColor = new Color(1f, 1f, 1f, 0.4f); + private NodeLaneMarker selectedMarker = null; + private NodeLaneMarker hoveredMarker = null; + private Dictionary> currentNodeMarkers; + private StayInLaneMode stayInLaneMode = StayInLaneMode.None; + //private bool initDone = false; + + /// Unity frame when OnGui detected the shortcut for Stay in Lane. + /// Resets when the event is consumed or after a few frames. + private int frameStayInLanePressed = 0; + + /// Clear lane lines is Delete/Backspace (configurable) + private int frameClearPressed = 0; + + class NodeLaneMarker { + internal ushort segmentId; + internal ushort nodeId; + internal bool startNode; + internal Vector3 position; + internal Vector3 secondaryPosition; + internal bool isSource; + internal bool isTarget; + internal uint laneId; + internal int innerSimilarLaneIndex; + internal int segmentIndex; + internal float radius = 1f; + internal Color color; + internal List connectedMarkers = new List(); + internal NetInfo.LaneType laneType; + internal VehicleInfo.VehicleType vehicleType; + } + + public LaneConnectorTool(TrafficManagerTool mainTool) : base(mainTool) { + //Log._Debug($"TppLaneConnectorTool: Constructor called"); + currentNodeMarkers = new Dictionary>(); + } + + public override void OnToolGUI(Event e) { // Log._Debug($"TppLaneConnectorTool: OnToolGUI. SelectedNodeId={SelectedNodeId} " + // $"SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} " + // $"HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); - if (KeymappingSettings.KeyLaneConnectorStayInLane.IsPressed(e)) { - frameStayInLanePressed = Time.frameCount; - // this will be consumed in RenderOverlay() if the key was pressed - // not too long ago (within 20 Unity frames or 0.33 sec) - } - - if (KeymappingSettings.KeyLaneConnectorDelete.IsPressed(e)) { - frameClearPressed = Time.frameCount; - // this will be consumed in RenderOverlay() if the key was pressed - // not too long ago (within 20 Unity frames or 0.33 sec) - } - } - - public override void RenderInfoOverlay(RenderManager.CameraInfo cameraInfo) { - ShowOverlay(true, cameraInfo); - } - - private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { - if (viewOnly && !Options.connectedLanesOverlay) - return; - - NetManager netManager = Singleton.instance; - - var camPos = Singleton.instance.m_simulationView.m_position; - //Bounds bounds = new Bounds(Vector3.zero, Vector3.one); - Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); - - for (uint nodeId = 1; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { - if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { - continue; - } - - // TODO refactor connection class check - ItemClass connectionClass = NetManager.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); - if (connectionClass == null || - !(connectionClass.m_service == ItemClass.Service.Road || (connectionClass.m_service == ItemClass.Service.PublicTransport && - (connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain || - connectionClass.m_subService == ItemClass.SubService.PublicTransportMetro || - connectionClass.m_subService == ItemClass.SubService.PublicTransportMonorail)))) { - continue; - } - - var diff = NetManager.instance.m_nodes.m_buffer[nodeId].m_position - camPos; - if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) - continue; // do not draw if too distant - - List nodeMarkers; - var hasMarkers = currentNodeMarkers.TryGetValue((ushort)nodeId, out nodeMarkers); - - if (!viewOnly && GetMarkerSelectionMode() == MarkerSelectionMode.None) { - MainTool.DrawNodeCircle(cameraInfo, (ushort)nodeId, DefaultNodeMarkerColor, true); - } - - if (hasMarkers) { - foreach (NodeLaneMarker laneMarker in nodeMarkers) { - if (!Constants.ServiceFactory.NetService.IsLaneValid(laneMarker.laneId)) { - continue; - } - - foreach (NodeLaneMarker targetLaneMarker in laneMarker.connectedMarkers) { - // render lane connection from laneMarker to targetLaneMarker - if (!Constants.ServiceFactory.NetService.IsLaneValid(targetLaneMarker.laneId)) { - continue; - } - RenderLane(cameraInfo, laneMarker.position, targetLaneMarker.position, NetManager.instance.m_nodes.m_buffer[nodeId].m_position, laneMarker.color); - } - - if (!viewOnly && nodeId == SelectedNodeId) { - //bounds.center = laneMarker.position; - bool markerIsHovered = IsLaneMarkerHovered(laneMarker, ref mouseRay);// bounds.IntersectRay(mouseRay); - - // draw source marker in source selection mode, - // draw target marker (if segment turning angles are within bounds) and selected source marker in target selection mode - bool drawMarker = (GetMarkerSelectionMode() == MarkerSelectionMode.SelectSource && laneMarker.isSource) || - (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget && ( - (laneMarker.isTarget && - (laneMarker.vehicleType & selectedMarker.vehicleType) != VehicleInfo.VehicleType.None && - CheckSegmentsTurningAngle(selectedMarker.segmentId, ref netManager.m_segments.m_buffer[selectedMarker.segmentId], selectedMarker.startNode, laneMarker.segmentId, ref netManager.m_segments.m_buffer[laneMarker.segmentId], laneMarker.startNode) - ) || laneMarker == selectedMarker)); - // highlight hovered marker and selected marker - bool highlightMarker = drawMarker && (laneMarker == selectedMarker || markerIsHovered); - - if (drawMarker) { - if (highlightMarker) { - laneMarker.radius = 2f; - } else - laneMarker.radius = 1f; - } else { - markerIsHovered = false; - } - - if (markerIsHovered) { - /*if (hoveredMarker != sourceLaneMarker) - Log._Debug($"Marker @ lane {sourceLaneMarker.laneId} hovered");*/ - hoveredMarker = laneMarker; - } - - if (drawMarker) { - //DrawLaneMarker(laneMarker, cameraInfo); - RenderManager.instance.OverlayEffect.DrawCircle(cameraInfo, laneMarker.color, laneMarker.position, laneMarker.radius, laneMarker.position.y - 100f, laneMarker.position.y + 100f, false, true); - } - } - } - } - } - } - - private bool IsLaneMarkerHovered(NodeLaneMarker laneMarker, ref Ray mouseRay) { - Bounds bounds = new Bounds(Vector3.zero, Vector3.one); - bounds.center = laneMarker.position; - if (bounds.IntersectRay(mouseRay)) - return true; - - bounds = new Bounds(Vector3.zero, Vector3.one); - bounds.center = laneMarker.secondaryPosition; - return bounds.IntersectRay(mouseRay); - } - - public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { - //Log._Debug($"TppLaneConnectorTool: RenderOverlay. SelectedNodeId={SelectedNodeId} SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); - // draw lane markers and connections - - hoveredMarker = null; - - ShowOverlay(false, cameraInfo); - - // draw bezier from source marker to mouse position in target marker selection - if (SelectedNodeId != 0) { - if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget) { - Vector3 selNodePos = NetManager.instance.m_nodes.m_buffer[SelectedNodeId].m_position; - - ToolBase.RaycastOutput output; - if (RayCastSegmentAndNode(out output)) { - RenderLane(cameraInfo, selectedMarker.position, output.m_hitPos, selNodePos, selectedMarker.color); - } - } - - var deleteAll = frameClearPressed > 0 && (Time.frameCount - frameClearPressed) < 20; // 0.33 sec - - // Must press Shift+S (or another shortcut) within last 20 frames for this to work - var stayInLane = frameStayInLanePressed > 0 - && (Time.frameCount - frameStayInLanePressed) < 20 // 0.33 sec - && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; - - if (stayInLane) { - frameStayInLanePressed = 0; // not pressed anymore (consumed) - deleteAll = true; - } - - if (deleteAll) { - frameClearPressed = 0; // consumed - // remove all connections at selected node - LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(SelectedNodeId); - RefreshCurrentNodeMarkers(SelectedNodeId); - } - - if (stayInLane) { - // "stay in lane" - switch (stayInLaneMode) { - case StayInLaneMode.None: - stayInLaneMode = StayInLaneMode.Both; - break; - case StayInLaneMode.Both: - stayInLaneMode = StayInLaneMode.Forward; - break; - case StayInLaneMode.Forward: - stayInLaneMode = StayInLaneMode.Backward; - break; - case StayInLaneMode.Backward: - stayInLaneMode = StayInLaneMode.None; - break; - } - - if (stayInLaneMode != StayInLaneMode.None) { - List nodeMarkers = GetNodeMarkers(SelectedNodeId, ref Singleton.instance.m_nodes.m_buffer[SelectedNodeId]); - if (nodeMarkers != null) { - selectedMarker = null; - foreach (NodeLaneMarker sourceLaneMarker in nodeMarkers) { - if (!sourceLaneMarker.isSource) - continue; - - if (stayInLaneMode == StayInLaneMode.Forward || stayInLaneMode == StayInLaneMode.Backward) { - if (sourceLaneMarker.segmentIndex == 0 ^ stayInLaneMode == StayInLaneMode.Backward) { - continue; - } - } - - foreach (NodeLaneMarker targetLaneMarker in nodeMarkers) { - if (!targetLaneMarker.isTarget || targetLaneMarker.segmentId == sourceLaneMarker.segmentId) - continue; - - if (targetLaneMarker.innerSimilarLaneIndex == sourceLaneMarker.innerSimilarLaneIndex) { - Log._Debug($"Adding lane connection {sourceLaneMarker.laneId} -> {targetLaneMarker.laneId}"); - LaneConnectionManager.Instance.AddLaneConnection(sourceLaneMarker.laneId, targetLaneMarker.laneId, sourceLaneMarker.startNode); - } - } - } - } - RefreshCurrentNodeMarkers(SelectedNodeId); - } - } - } - - if (GetMarkerSelectionMode() == MarkerSelectionMode.None && HoveredNodeId != 0) { - // draw hovered node - MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); - } - } - - public override void OnPrimaryClickOverlay() { + if (KeymappingSettings.KeyLaneConnectorStayInLane.IsPressed(e)) { + frameStayInLanePressed = Time.frameCount; + // this will be consumed in RenderOverlay() if the key was pressed + // not too long ago (within 20 Unity frames or 0.33 sec) + } + + if (KeymappingSettings.KeyLaneConnectorDelete.IsPressed(e)) { + frameClearPressed = Time.frameCount; + // this will be consumed in RenderOverlay() if the key was pressed + // not too long ago (within 20 Unity frames or 0.33 sec) + } + } + + public override void RenderInfoOverlay(RenderManager.CameraInfo cameraInfo) { + ShowOverlay(true, cameraInfo); + } + + private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { + if (viewOnly && !Options.connectedLanesOverlay) + return; + + NetManager netManager = Singleton.instance; + + var camPos = Singleton.instance.m_simulationView.m_position; + //Bounds bounds = new Bounds(Vector3.zero, Vector3.one); + Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); + + for (uint nodeId = 1; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { + if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { + continue; + } + + // TODO refactor connection class check + ItemClass connectionClass = NetManager.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); + if (connectionClass == null || + !(connectionClass.m_service == ItemClass.Service.Road || (connectionClass.m_service == ItemClass.Service.PublicTransport && + (connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain || + connectionClass.m_subService == ItemClass.SubService.PublicTransportMetro || + connectionClass.m_subService == ItemClass.SubService.PublicTransportMonorail)))) { + continue; + } + + var diff = NetManager.instance.m_nodes.m_buffer[nodeId].m_position - camPos; + if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) + continue; // do not draw if too distant + + List nodeMarkers; + var hasMarkers = currentNodeMarkers.TryGetValue((ushort)nodeId, out nodeMarkers); + + if (!viewOnly && GetMarkerSelectionMode() == MarkerSelectionMode.None) { + MainTool.DrawNodeCircle(cameraInfo, (ushort)nodeId, DefaultNodeMarkerColor, true); + } + + if (hasMarkers) { + foreach (NodeLaneMarker laneMarker in nodeMarkers) { + if (!Constants.ServiceFactory.NetService.IsLaneValid(laneMarker.laneId)) { + continue; + } + + foreach (NodeLaneMarker targetLaneMarker in laneMarker.connectedMarkers) { + // render lane connection from laneMarker to targetLaneMarker + if (!Constants.ServiceFactory.NetService.IsLaneValid(targetLaneMarker.laneId)) { + continue; + } + RenderLane(cameraInfo, laneMarker.position, targetLaneMarker.position, NetManager.instance.m_nodes.m_buffer[nodeId].m_position, laneMarker.color); + } + + if (!viewOnly && nodeId == SelectedNodeId) { + //bounds.center = laneMarker.position; + bool markerIsHovered = IsLaneMarkerHovered(laneMarker, ref mouseRay);// bounds.IntersectRay(mouseRay); + + // draw source marker in source selection mode, + // draw target marker (if segment turning angles are within bounds) and selected source marker in target selection mode + bool drawMarker = (GetMarkerSelectionMode() == MarkerSelectionMode.SelectSource && laneMarker.isSource) || + (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget && ( + (laneMarker.isTarget && + (laneMarker.vehicleType & selectedMarker.vehicleType) != VehicleInfo.VehicleType.None && + CheckSegmentsTurningAngle(selectedMarker.segmentId, ref netManager.m_segments.m_buffer[selectedMarker.segmentId], selectedMarker.startNode, laneMarker.segmentId, ref netManager.m_segments.m_buffer[laneMarker.segmentId], laneMarker.startNode) + ) || laneMarker == selectedMarker)); + // highlight hovered marker and selected marker + bool highlightMarker = drawMarker && (laneMarker == selectedMarker || markerIsHovered); + + if (drawMarker) { + if (highlightMarker) { + laneMarker.radius = 2f; + } else + laneMarker.radius = 1f; + } else { + markerIsHovered = false; + } + + if (markerIsHovered) { + /*if (hoveredMarker != sourceLaneMarker) + Log._Debug($"Marker @ lane {sourceLaneMarker.laneId} hovered");*/ + hoveredMarker = laneMarker; + } + + if (drawMarker) { + //DrawLaneMarker(laneMarker, cameraInfo); + RenderManager.instance.OverlayEffect.DrawCircle(cameraInfo, laneMarker.color, laneMarker.position, laneMarker.radius, laneMarker.position.y - 100f, laneMarker.position.y + 100f, false, true); + } + } + } + } + } + } + + private bool IsLaneMarkerHovered(NodeLaneMarker laneMarker, ref Ray mouseRay) { + Bounds bounds = new Bounds(Vector3.zero, Vector3.one); + bounds.center = laneMarker.position; + if (bounds.IntersectRay(mouseRay)) + return true; + + bounds = new Bounds(Vector3.zero, Vector3.one); + bounds.center = laneMarker.secondaryPosition; + return bounds.IntersectRay(mouseRay); + } + + public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { + //Log._Debug($"TppLaneConnectorTool: RenderOverlay. SelectedNodeId={SelectedNodeId} SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); + // draw lane markers and connections + + hoveredMarker = null; + + ShowOverlay(false, cameraInfo); + + // draw bezier from source marker to mouse position in target marker selection + if (SelectedNodeId != 0) { + if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget) { + Vector3 selNodePos = NetManager.instance.m_nodes.m_buffer[SelectedNodeId].m_position; + + ToolBase.RaycastOutput output; + if (RayCastSegmentAndNode(out output)) { + RenderLane(cameraInfo, selectedMarker.position, output.m_hitPos, selNodePos, selectedMarker.color); + } + } + + var deleteAll = frameClearPressed > 0 && (Time.frameCount - frameClearPressed) < 20; // 0.33 sec + + // Must press Shift+S (or another shortcut) within last 20 frames for this to work + var stayInLane = frameStayInLanePressed > 0 + && (Time.frameCount - frameStayInLanePressed) < 20 // 0.33 sec + && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; + + if (stayInLane) { + frameStayInLanePressed = 0; // not pressed anymore (consumed) + deleteAll = true; + } + + if (deleteAll) { + frameClearPressed = 0; // consumed + // remove all connections at selected node + LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(SelectedNodeId); + RefreshCurrentNodeMarkers(SelectedNodeId); + } + + if (stayInLane) { + // "stay in lane" + switch (stayInLaneMode) { + case StayInLaneMode.None: + stayInLaneMode = StayInLaneMode.Both; + break; + case StayInLaneMode.Both: + stayInLaneMode = StayInLaneMode.Forward; + break; + case StayInLaneMode.Forward: + stayInLaneMode = StayInLaneMode.Backward; + break; + case StayInLaneMode.Backward: + stayInLaneMode = StayInLaneMode.None; + break; + } + + if (stayInLaneMode != StayInLaneMode.None) { + List nodeMarkers = GetNodeMarkers(SelectedNodeId, ref Singleton.instance.m_nodes.m_buffer[SelectedNodeId]); + if (nodeMarkers != null) { + selectedMarker = null; + foreach (NodeLaneMarker sourceLaneMarker in nodeMarkers) { + if (!sourceLaneMarker.isSource) + continue; + + if (stayInLaneMode == StayInLaneMode.Forward || stayInLaneMode == StayInLaneMode.Backward) { + if (sourceLaneMarker.segmentIndex == 0 ^ stayInLaneMode == StayInLaneMode.Backward) { + continue; + } + } + + foreach (NodeLaneMarker targetLaneMarker in nodeMarkers) { + if (!targetLaneMarker.isTarget || targetLaneMarker.segmentId == sourceLaneMarker.segmentId) + continue; + + if (targetLaneMarker.innerSimilarLaneIndex == sourceLaneMarker.innerSimilarLaneIndex) { + Log._Debug($"Adding lane connection {sourceLaneMarker.laneId} -> {targetLaneMarker.laneId}"); + LaneConnectionManager.Instance.AddLaneConnection(sourceLaneMarker.laneId, targetLaneMarker.laneId, sourceLaneMarker.startNode); + } + } + } + } + RefreshCurrentNodeMarkers(SelectedNodeId); + } + } + } + + if (GetMarkerSelectionMode() == MarkerSelectionMode.None && HoveredNodeId != 0) { + // draw hovered node + MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); + } + } + + public override void OnPrimaryClickOverlay() { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"TppLaneConnectorTool: OnPrimaryClickOverlay. SelectedNodeId={SelectedNodeId} SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} HoveredSegmentId={HoveredSegmentId}"); #endif - if (IsCursorInPanel()) - return; + if (IsCursorInPanel()) + return; - if (GetMarkerSelectionMode() == MarkerSelectionMode.None) { - if (HoveredNodeId != 0) { + if (GetMarkerSelectionMode() == MarkerSelectionMode.None) { + if (HoveredNodeId != 0) { #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: HoveredNode != 0"); #endif - if (NetManager.instance.m_nodes.m_buffer[HoveredNodeId].CountSegments() < 2) { - // this node cannot be configured (dead end) + if (NetManager.instance.m_nodes.m_buffer[HoveredNodeId].CountSegments() < 2) { + // this node cannot be configured (dead end) #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: Node is a dead end"); #endif - SelectedNodeId = 0; - selectedMarker = null; - stayInLaneMode = StayInLaneMode.None; - return; - } + SelectedNodeId = 0; + selectedMarker = null; + stayInLaneMode = StayInLaneMode.None; + return; + } - if (SelectedNodeId != HoveredNodeId) { + if (SelectedNodeId != HoveredNodeId) { #if DEBUGCONN if (debug) Log._Debug($"Node {HoveredNodeId} has been selected. Creating markers."); #endif - // selected node has changed. create markers - List markers = GetNodeMarkers(HoveredNodeId, ref Singleton.instance.m_nodes.m_buffer[HoveredNodeId]); - if (markers != null) { - SelectedNodeId = HoveredNodeId; - selectedMarker = null; - stayInLaneMode = StayInLaneMode.None; - - currentNodeMarkers[SelectedNodeId] = markers; - } - //this.allNodeMarkers[SelectedNodeId] = GetNodeMarkers(SelectedNodeId); - } - } else { + // selected node has changed. create markers + List markers = GetNodeMarkers(HoveredNodeId, ref Singleton.instance.m_nodes.m_buffer[HoveredNodeId]); + if (markers != null) { + SelectedNodeId = HoveredNodeId; + selectedMarker = null; + stayInLaneMode = StayInLaneMode.None; + + currentNodeMarkers[SelectedNodeId] = markers; + } + //this.allNodeMarkers[SelectedNodeId] = GetNodeMarkers(SelectedNodeId); + } + } else { #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: Node {SelectedNodeId} has been deselected."); #endif - // click on free spot. deselect node - SelectedNodeId = 0; - selectedMarker = null; - stayInLaneMode = StayInLaneMode.None; - return; - } - } + // click on free spot. deselect node + SelectedNodeId = 0; + selectedMarker = null; + stayInLaneMode = StayInLaneMode.None; + return; + } + } - if (hoveredMarker != null) { - stayInLaneMode = StayInLaneMode.None; + if (hoveredMarker != null) { + stayInLaneMode = StayInLaneMode.None; #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: hoveredMarker != null. selMode={GetMarkerSelectionMode()}"); #endif - // hovered marker has been clicked - if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectSource) { - // select source marker - selectedMarker = hoveredMarker; + // hovered marker has been clicked + if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectSource) { + // select source marker + selectedMarker = hoveredMarker; #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: set selected marker"); #endif - } else if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget) { - // select target marker - //bool success = false; - if (LaneConnectionManager.Instance.RemoveLaneConnection(selectedMarker.laneId, hoveredMarker.laneId, selectedMarker.startNode)) { // try to remove connection - selectedMarker.connectedMarkers.Remove(hoveredMarker); + } else if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget) { + // select target marker + //bool success = false; + if (LaneConnectionManager.Instance.RemoveLaneConnection(selectedMarker.laneId, hoveredMarker.laneId, selectedMarker.startNode)) { // try to remove connection + selectedMarker.connectedMarkers.Remove(hoveredMarker); #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: removed lane connection: {selectedMarker.laneId}, {hoveredMarker.laneId}"); #endif - //success = true; - } else if (LaneConnectionManager.Instance.AddLaneConnection(selectedMarker.laneId, hoveredMarker.laneId, selectedMarker.startNode)) { // try to add connection - selectedMarker.connectedMarkers.Add(hoveredMarker); + //success = true; + } else if (LaneConnectionManager.Instance.AddLaneConnection(selectedMarker.laneId, hoveredMarker.laneId, selectedMarker.startNode)) { // try to add connection + selectedMarker.connectedMarkers.Add(hoveredMarker); #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: added lane connection: {selectedMarker.laneId}, {hoveredMarker.laneId}"); #endif - //success = true; - } - - /*if (success) { - // connection has been modified. switch back to source marker selection - Log._Debug($"TppLaneConnectorTool: switch back to source marker selection"); - selectedMarker = null; - selMode = MarkerSelectionMode.SelectSource; - }*/ - } - } - } - - public override void OnSecondaryClickOverlay() { + //success = true; + } + + /*if (success) { + // connection has been modified. switch back to source marker selection + Log._Debug($"TppLaneConnectorTool: switch back to source marker selection"); + selectedMarker = null; + selMode = MarkerSelectionMode.SelectSource; + }*/ + } + } + } + + public override void OnSecondaryClickOverlay() { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; #endif - if (IsCursorInPanel()) - return; + if (IsCursorInPanel()) + return; - switch (GetMarkerSelectionMode()) { - case MarkerSelectionMode.None: - default: + switch (GetMarkerSelectionMode()) { + case MarkerSelectionMode.None: + default: #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: OnSecondaryClickOverlay: nothing to do"); #endif - stayInLaneMode = StayInLaneMode.None; - break; - case MarkerSelectionMode.SelectSource: - // deselect node + stayInLaneMode = StayInLaneMode.None; + break; + case MarkerSelectionMode.SelectSource: + // deselect node #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: OnSecondaryClickOverlay: selected node id = 0"); #endif - SelectedNodeId = 0; - break; - case MarkerSelectionMode.SelectTarget: - // deselect source marker + SelectedNodeId = 0; + break; + case MarkerSelectionMode.SelectTarget: + // deselect source marker #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: OnSecondaryClickOverlay: switch to selected source mode"); #endif - selectedMarker = null; - break; - } - } + selectedMarker = null; + break; + } + } - public override void OnActivate() { + public override void OnActivate() { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug("TppLaneConnectorTool: OnActivate"); #endif - SelectedNodeId = 0; - selectedMarker = null; - hoveredMarker = null; - stayInLaneMode = StayInLaneMode.None; - RefreshCurrentNodeMarkers(); - } - - private void RefreshCurrentNodeMarkers(ushort forceNodeId=0) { - if (forceNodeId == 0) { - currentNodeMarkers.Clear(); - } else { - currentNodeMarkers.Remove(forceNodeId); - } - - for (uint nodeId = (forceNodeId == 0 ? 1u : forceNodeId); nodeId <= (forceNodeId == 0 ? NetManager.MAX_NODE_COUNT-1 : forceNodeId); ++nodeId) { - if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { - continue; - } - - if (! LaneConnectionManager.Instance.HasNodeConnections((ushort)nodeId)) { - continue; - } - - List nodeMarkers = GetNodeMarkers((ushort)nodeId, ref Singleton.instance.m_nodes.m_buffer[nodeId]); - if (nodeMarkers == null) - continue; - currentNodeMarkers[(ushort)nodeId] = nodeMarkers; - } - } - - private MarkerSelectionMode GetMarkerSelectionMode() { - if (SelectedNodeId == 0) - return MarkerSelectionMode.None; - if (selectedMarker == null) - return MarkerSelectionMode.SelectSource; - return MarkerSelectionMode.SelectTarget; - } - - public override void Cleanup() { - - } - - public override void Initialize() { - base.Initialize(); - Cleanup(); - if (Options.connectedLanesOverlay) { - RefreshCurrentNodeMarkers(); - } else { - currentNodeMarkers.Clear(); - } - } - - private List GetNodeMarkers(ushort nodeId, ref NetNode node) { - if (nodeId == 0) - return null; - if ((node.m_flags & NetNode.Flags.Created) == NetNode.Flags.None) - return null; - - List nodeMarkers = new List(); - LaneConnectionManager connManager = LaneConnectionManager.Instance; - - int offsetMultiplier = node.CountSegments() <= 2 ? 3 : 1; - for (int i = 0; i < 8; i++) { - ushort segmentId = node.GetSegment(i); - if (segmentId == 0) - continue; - - bool isEndNode = NetManager.instance.m_segments.m_buffer[segmentId].m_endNode == nodeId; - Vector3 offset = NetManager.instance.m_segments.m_buffer[segmentId].FindDirection(segmentId, nodeId) * offsetMultiplier; - NetInfo.Lane[] lanes = NetManager.instance.m_segments.m_buffer[segmentId].Info.m_lanes; - uint laneId = NetManager.instance.m_segments.m_buffer[segmentId].m_lanes; - for (byte laneIndex = 0; laneIndex < lanes.Length && laneId != 0; laneIndex++) { - NetInfo.Lane laneInfo = lanes[laneIndex]; - if ((laneInfo.m_laneType & LaneConnectionManager.LANE_TYPES) != NetInfo.LaneType.None && - (laneInfo.m_vehicleType & LaneConnectionManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { - - Vector3? pos = null; - bool isSource = false; - bool isTarget = false; - if (connManager.GetLaneEndPoint(segmentId, !isEndNode, laneIndex, laneId, laneInfo, out isSource, out isTarget, out pos)) { - - pos = (Vector3)pos + offset; - float terrainY = Singleton.instance.SampleDetailHeightSmooth(((Vector3)pos)); - Vector3 finalPos = new Vector3(((Vector3)pos).x, terrainY, ((Vector3)pos).z); - - nodeMarkers.Add(new NodeLaneMarker() { - segmentId = segmentId, - laneId = laneId, - nodeId = nodeId, - startNode = !isEndNode, - position = finalPos, - secondaryPosition = (Vector3)pos, - color = colors[nodeMarkers.Count % colors.Length], - isSource = isSource, - isTarget = isTarget, - laneType = laneInfo.m_laneType, - vehicleType = laneInfo.m_vehicleType, - innerSimilarLaneIndex = ((byte)(laneInfo.m_direction & NetInfo.Direction.Forward) != 0) ? laneInfo.m_similarLaneIndex : laneInfo.m_similarLaneCount - laneInfo.m_similarLaneIndex - 1, - segmentIndex = i - }); - } - } - - laneId = NetManager.instance.m_lanes.m_buffer[laneId].m_nextLane; - } - } - - if (nodeMarkers.Count == 0) - return null; - - foreach (NodeLaneMarker laneMarker1 in nodeMarkers) { - if (!laneMarker1.isSource) - continue; - - uint[] connections = LaneConnectionManager.Instance.GetLaneConnections(laneMarker1.laneId, laneMarker1.startNode); - if (connections == null || connections.Length == 0) - continue; - - foreach (NodeLaneMarker laneMarker2 in nodeMarkers) { - if (!laneMarker2.isTarget) - continue; - - if (connections.Contains(laneMarker2.laneId)) - laneMarker1.connectedMarkers.Add(laneMarker2); - } - } - - return nodeMarkers; - } - - /// - /// Checks if the turning angle between two segments at the given node is within bounds. - /// - /// - /// - /// - /// - /// - /// - /// - private bool CheckSegmentsTurningAngle(ushort sourceSegmentId, ref NetSegment sourceSegment, bool sourceStartNode, ushort targetSegmentId, ref NetSegment targetSegment, bool targetStartNode) { - NetManager netManager = Singleton.instance; - - NetInfo sourceSegmentInfo = netManager.m_segments.m_buffer[sourceSegmentId].Info; - NetInfo targetSegmentInfo = netManager.m_segments.m_buffer[targetSegmentId].Info; - - float turningAngle = 0.01f - Mathf.Min(sourceSegmentInfo.m_maxTurnAngleCos, targetSegmentInfo.m_maxTurnAngleCos); - if (turningAngle < 1f) { - Vector3 sourceDirection; - if (sourceStartNode) { - sourceDirection = sourceSegment.m_startDirection; - } else { - sourceDirection = sourceSegment.m_endDirection; - } - - Vector3 targetDirection; - if (targetStartNode) { - targetDirection = targetSegment.m_startDirection; - } else { - targetDirection = targetSegment.m_endDirection; - } - float dirDotProd = sourceDirection.x * targetDirection.x + sourceDirection.z * targetDirection.z; - return dirDotProd < turningAngle; - } - return true; - } - - private void RenderLane(RenderManager.CameraInfo cameraInfo, Vector3 start, Vector3 end, Vector3 middlePoint, Color color, float size = 0.1f) { - Bezier3 bezier; - bezier.a = start; - bezier.d = end; - NetSegment.CalculateMiddlePoints(bezier.a, (middlePoint - bezier.a).normalized, bezier.d, (middlePoint - bezier.d).normalized, false, false, out bezier.b, out bezier.c); - - RenderManager.instance.OverlayEffect.DrawBezier(cameraInfo, color, bezier, size, 0, 0, -1f, 1280f, false, true); - } - - private bool RayCastSegmentAndNode(out ToolBase.RaycastOutput output) { - ToolBase.RaycastInput input = new ToolBase.RaycastInput(Camera.main.ScreenPointToRay(Input.mousePosition), Camera.main.farClipPlane); - input.m_netService.m_service = ItemClass.Service.Road; - input.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; - input.m_ignoreSegmentFlags = NetSegment.Flags.None; - input.m_ignoreNodeFlags = NetNode.Flags.None; - input.m_ignoreTerrain = true; - - return MainTool.DoRayCast(input, out output); - } - - private static readonly Color32[] colors = new Color32[] - { - new Color32(161, 64, 206, 255), - new Color32(79, 251, 8, 255), - new Color32(243, 96, 44, 255), - new Color32(45, 106, 105, 255), - new Color32(253, 165, 187, 255), - new Color32(90, 131, 14, 255), - new Color32(58, 20, 70, 255), - new Color32(248, 246, 183, 255), - new Color32(255, 205, 29, 255), - new Color32(91, 50, 18, 255), - new Color32(76, 239, 155, 255), - new Color32(241, 25, 130, 255), - new Color32(125, 197, 240, 255), - new Color32(57, 102, 187, 255), - new Color32(160, 27, 61, 255), - new Color32(167, 251, 107, 255), - new Color32(165, 94, 3, 255), - new Color32(204, 18, 161, 255), - new Color32(208, 136, 237, 255), - new Color32(232, 211, 202, 255), - new Color32(45, 182, 15, 255), - new Color32(8, 40, 47, 255), - new Color32(249, 172, 142, 255), - new Color32(248, 99, 101, 255), - new Color32(180, 250, 208, 255), - new Color32(126, 25, 77, 255), - new Color32(243, 170, 55, 255), - new Color32(47, 69, 126, 255), - new Color32(50, 105, 70, 255), - new Color32(156, 49, 1, 255), - new Color32(233, 231, 255, 255), - new Color32(107, 146, 253, 255), - new Color32(127, 35, 26, 255), - new Color32(240, 94, 222, 255), - new Color32(58, 28, 24, 255), - new Color32(165, 179, 240, 255), - new Color32(239, 93, 145, 255), - new Color32(47, 110, 138, 255), - new Color32(57, 195, 101, 255), - new Color32(124, 88, 213, 255), - new Color32(252, 220, 144, 255), - new Color32(48, 106, 224, 255), - new Color32(90, 109, 28, 255), - new Color32(56, 179, 208, 255), - new Color32(239, 73, 177, 255), - new Color32(84, 60, 2, 255), - new Color32(169, 104, 238, 255), - new Color32(97, 201, 238, 255), - }; - } -} + SelectedNodeId = 0; + selectedMarker = null; + hoveredMarker = null; + stayInLaneMode = StayInLaneMode.None; + RefreshCurrentNodeMarkers(); + } + + private void RefreshCurrentNodeMarkers(ushort forceNodeId=0) { + if (forceNodeId == 0) { + currentNodeMarkers.Clear(); + } else { + currentNodeMarkers.Remove(forceNodeId); + } + + for (uint nodeId = (forceNodeId == 0 ? 1u : forceNodeId); nodeId <= (forceNodeId == 0 ? NetManager.MAX_NODE_COUNT-1 : forceNodeId); ++nodeId) { + if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { + continue; + } + + if (! LaneConnectionManager.Instance.HasNodeConnections((ushort)nodeId)) { + continue; + } + + List nodeMarkers = GetNodeMarkers((ushort)nodeId, ref Singleton.instance.m_nodes.m_buffer[nodeId]); + if (nodeMarkers == null) + continue; + currentNodeMarkers[(ushort)nodeId] = nodeMarkers; + } + } + + private MarkerSelectionMode GetMarkerSelectionMode() { + if (SelectedNodeId == 0) + return MarkerSelectionMode.None; + if (selectedMarker == null) + return MarkerSelectionMode.SelectSource; + return MarkerSelectionMode.SelectTarget; + } + + public override void Cleanup() { + + } + + public override void Initialize() { + base.Initialize(); + Cleanup(); + if (Options.connectedLanesOverlay) { + RefreshCurrentNodeMarkers(); + } else { + currentNodeMarkers.Clear(); + } + } + + private List GetNodeMarkers(ushort nodeId, ref NetNode node) { + if (nodeId == 0) + return null; + if ((node.m_flags & NetNode.Flags.Created) == NetNode.Flags.None) + return null; + + List nodeMarkers = new List(); + LaneConnectionManager connManager = LaneConnectionManager.Instance; + + int offsetMultiplier = node.CountSegments() <= 2 ? 3 : 1; + for (int i = 0; i < 8; i++) { + ushort segmentId = node.GetSegment(i); + if (segmentId == 0) + continue; + + bool isEndNode = NetManager.instance.m_segments.m_buffer[segmentId].m_endNode == nodeId; + Vector3 offset = NetManager.instance.m_segments.m_buffer[segmentId].FindDirection(segmentId, nodeId) * offsetMultiplier; + NetInfo.Lane[] lanes = NetManager.instance.m_segments.m_buffer[segmentId].Info.m_lanes; + uint laneId = NetManager.instance.m_segments.m_buffer[segmentId].m_lanes; + for (byte laneIndex = 0; laneIndex < lanes.Length && laneId != 0; laneIndex++) { + NetInfo.Lane laneInfo = lanes[laneIndex]; + if ((laneInfo.m_laneType & LaneConnectionManager.LANE_TYPES) != NetInfo.LaneType.None && + (laneInfo.m_vehicleType & LaneConnectionManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { + + Vector3? pos = null; + bool isSource = false; + bool isTarget = false; + if (connManager.GetLaneEndPoint(segmentId, !isEndNode, laneIndex, laneId, laneInfo, out isSource, out isTarget, out pos)) { + + pos = (Vector3)pos + offset; + float terrainY = Singleton.instance.SampleDetailHeightSmooth(((Vector3)pos)); + Vector3 finalPos = new Vector3(((Vector3)pos).x, terrainY, ((Vector3)pos).z); + + nodeMarkers.Add(new NodeLaneMarker() { + segmentId = segmentId, + laneId = laneId, + nodeId = nodeId, + startNode = !isEndNode, + position = finalPos, + secondaryPosition = (Vector3)pos, + color = colors[nodeMarkers.Count % colors.Length], + isSource = isSource, + isTarget = isTarget, + laneType = laneInfo.m_laneType, + vehicleType = laneInfo.m_vehicleType, + innerSimilarLaneIndex = ((byte)(laneInfo.m_direction & NetInfo.Direction.Forward) != 0) ? laneInfo.m_similarLaneIndex : laneInfo.m_similarLaneCount - laneInfo.m_similarLaneIndex - 1, + segmentIndex = i + }); + } + } + + laneId = NetManager.instance.m_lanes.m_buffer[laneId].m_nextLane; + } + } + + if (nodeMarkers.Count == 0) + return null; + + foreach (NodeLaneMarker laneMarker1 in nodeMarkers) { + if (!laneMarker1.isSource) + continue; + + uint[] connections = LaneConnectionManager.Instance.GetLaneConnections(laneMarker1.laneId, laneMarker1.startNode); + if (connections == null || connections.Length == 0) + continue; + + foreach (NodeLaneMarker laneMarker2 in nodeMarkers) { + if (!laneMarker2.isTarget) + continue; + + if (connections.Contains(laneMarker2.laneId)) + laneMarker1.connectedMarkers.Add(laneMarker2); + } + } + + return nodeMarkers; + } + + /// + /// Checks if the turning angle between two segments at the given node is within bounds. + /// + /// + /// + /// + /// + /// + /// + /// + private bool CheckSegmentsTurningAngle(ushort sourceSegmentId, ref NetSegment sourceSegment, bool sourceStartNode, ushort targetSegmentId, ref NetSegment targetSegment, bool targetStartNode) { + NetManager netManager = Singleton.instance; + + NetInfo sourceSegmentInfo = netManager.m_segments.m_buffer[sourceSegmentId].Info; + NetInfo targetSegmentInfo = netManager.m_segments.m_buffer[targetSegmentId].Info; + + float turningAngle = 0.01f - Mathf.Min(sourceSegmentInfo.m_maxTurnAngleCos, targetSegmentInfo.m_maxTurnAngleCos); + if (turningAngle < 1f) { + Vector3 sourceDirection; + if (sourceStartNode) { + sourceDirection = sourceSegment.m_startDirection; + } else { + sourceDirection = sourceSegment.m_endDirection; + } + + Vector3 targetDirection; + if (targetStartNode) { + targetDirection = targetSegment.m_startDirection; + } else { + targetDirection = targetSegment.m_endDirection; + } + float dirDotProd = sourceDirection.x * targetDirection.x + sourceDirection.z * targetDirection.z; + return dirDotProd < turningAngle; + } + return true; + } + + private void RenderLane(RenderManager.CameraInfo cameraInfo, Vector3 start, Vector3 end, Vector3 middlePoint, Color color, float size = 0.1f) { + Bezier3 bezier; + bezier.a = start; + bezier.d = end; + NetSegment.CalculateMiddlePoints(bezier.a, (middlePoint - bezier.a).normalized, bezier.d, (middlePoint - bezier.d).normalized, false, false, out bezier.b, out bezier.c); + + RenderManager.instance.OverlayEffect.DrawBezier(cameraInfo, color, bezier, size, 0, 0, -1f, 1280f, false, true); + } + + private bool RayCastSegmentAndNode(out ToolBase.RaycastOutput output) { + ToolBase.RaycastInput input = new ToolBase.RaycastInput(Camera.main.ScreenPointToRay(Input.mousePosition), Camera.main.farClipPlane); + input.m_netService.m_service = ItemClass.Service.Road; + input.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; + input.m_ignoreSegmentFlags = NetSegment.Flags.None; + input.m_ignoreNodeFlags = NetNode.Flags.None; + input.m_ignoreTerrain = true; + + return MainTool.DoRayCast(input, out output); + } + + private static readonly Color32[] colors + = { + new Color32(161, 64, 206, 255), + new Color32(79, 251, 8, 255), + new Color32(243, 96, 44, 255), + new Color32(45, 106, 105, 255), + new Color32(253, 165, 187, 255), + new Color32(90, 131, 14, 255), + new Color32(58, 20, 70, 255), + new Color32(248, 246, 183, 255), + new Color32(255, 205, 29, 255), + new Color32(91, 50, 18, 255), + new Color32(76, 239, 155, 255), + new Color32(241, 25, 130, 255), + new Color32(125, 197, 240, 255), + new Color32(57, 102, 187, 255), + new Color32(160, 27, 61, 255), + new Color32(167, 251, 107, 255), + new Color32(165, 94, 3, 255), + new Color32(204, 18, 161, 255), + new Color32(208, 136, 237, 255), + new Color32(232, 211, 202, 255), + new Color32(45, 182, 15, 255), + new Color32(8, 40, 47, 255), + new Color32(249, 172, 142, 255), + new Color32(248, 99, 101, 255), + new Color32(180, 250, 208, 255), + new Color32(126, 25, 77, 255), + new Color32(243, 170, 55, 255), + new Color32(47, 69, 126, 255), + new Color32(50, 105, 70, 255), + new Color32(156, 49, 1, 255), + new Color32(233, 231, 255, 255), + new Color32(107, 146, 253, 255), + new Color32(127, 35, 26, 255), + new Color32(240, 94, 222, 255), + new Color32(58, 28, 24, 255), + new Color32(165, 179, 240, 255), + new Color32(239, 93, 145, 255), + new Color32(47, 110, 138, 255), + new Color32(57, 195, 101, 255), + new Color32(124, 88, 213, 255), + new Color32(252, 220, 144, 255), + new Color32(48, 106, 224, 255), + new Color32(90, 109, 28, 255), + new Color32(56, 179, 208, 255), + new Color32(239, 73, 177, 255), + new Color32(84, 60, 2, 255), + new Color32(169, 104, 238, 255), + new Color32(97, 201, 238, 255), + }; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/ToolMode.cs b/TLM/TLM/UI/ToolMode.cs index 17518401c..af4ffa4b1 100644 --- a/TLM/TLM/UI/ToolMode.cs +++ b/TLM/TLM/UI/ToolMode.cs @@ -1,19 +1,19 @@ namespace TrafficManager.UI { - public enum ToolMode { - None = 0, - SwitchTrafficLight = 1, - AddPrioritySigns = 2, - ManualSwitch = 3, - TimedLightsSelectNode = 4, - TimedLightsShowLights = 5, - LaneChange = 6, - TimedLightsAddNode = 7, - TimedLightsRemoveNode = 8, - TimedLightsCopyLights = 9, - SpeedLimits = 10, - VehicleRestrictions = 11, - LaneConnector = 12, - JunctionRestrictions = 13, - ParkingRestrictions = 14 - } + public enum ToolMode { + None = 0, + SwitchTrafficLight = 1, + AddPrioritySigns = 2, + ManualSwitch = 3, + TimedLightsSelectNode = 4, + TimedLightsShowLights = 5, + LaneChange = 6, + TimedLightsAddNode = 7, + TimedLightsRemoveNode = 8, + TimedLightsCopyLights = 9, + SpeedLimits = 10, + VehicleRestrictions = 11, + LaneConnector = 12, + JunctionRestrictions = 13, + ParkingRestrictions = 14 + } } \ No newline at end of file diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 988666ef2..bdd2e7570 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -6,11 +6,8 @@ using ColossalFramework.UI; using JetBrains.Annotations; using TrafficManager.Custom.AI; -using TrafficManager.Geometry; -using TrafficManager.UI; using UnityEngine; using TrafficManager.State; -using TrafficManager.TrafficLight; using TrafficManager.UI.SubTools; using TrafficManager.Traffic; using TrafficManager.Manager; @@ -20,715 +17,714 @@ using TrafficManager.Manager.Impl; using TrafficManager.Traffic.Data; using static TrafficManager.Traffic.Data.ExtCitizenInstance; -using System.Collections; namespace TrafficManager.UI { - [UsedImplicitly] - public class TrafficManagerTool : DefaultTool, IObserver { - public struct NodeVisitItem { - public ushort nodeId; - public bool startNode; - - public NodeVisitItem(ushort nodeId, bool startNode) { - this.nodeId = nodeId; - this.startNode = startNode; - } - } + [UsedImplicitly] + public class TrafficManagerTool : DefaultTool, IObserver { + public struct NodeVisitItem { + public ushort nodeId; + public bool startNode; + + public NodeVisitItem(ushort nodeId, bool startNode) { + this.nodeId = nodeId; + this.startNode = startNode; + } + } - private ToolMode _toolMode; + private ToolMode _toolMode; - internal static ushort HoveredNodeId; - internal static ushort HoveredSegmentId; + internal static ushort HoveredNodeId; + internal static ushort HoveredSegmentId; - private static bool mouseClickProcessed; + private static bool mouseClickProcessed; - public static readonly float DebugCloseLod = 300f; - public static readonly float MaxOverlayDistance = 450f; + public static readonly float DebugCloseLod = 300f; + public static readonly float MaxOverlayDistance = 450f; - private IDictionary subTools = new TinyDictionary(); + private IDictionary subTools = new TinyDictionary(); - public static ushort SelectedNodeId { get; internal set; } + public static ushort SelectedNodeId { get; internal set; } - public static ushort SelectedSegmentId { get; internal set; } + public static ushort SelectedSegmentId { get; internal set; } public static TransportDemandViewMode CurrentTransportDemandViewMode { get; internal set; } = TransportDemandViewMode.Outgoing; internal static ExtVehicleType[] InfoSignsToDisplay = new ExtVehicleType[] { ExtVehicleType.PassengerCar, ExtVehicleType.Bicycle, ExtVehicleType.Bus, ExtVehicleType.Taxi, ExtVehicleType.Tram, ExtVehicleType.CargoTruck, ExtVehicleType.Service, ExtVehicleType.RailVehicle }; - private static SubTool activeSubTool = null; - - private static IDisposable confDisposable; - - static TrafficManagerTool() { - - } - - internal ToolController GetToolController() { - return m_toolController; - } - - internal static Rect MoveGUI(Rect rect) { - // x := main menu x + rect.x - // y := main menu y + main menu height + rect.y - return new Rect(MainMenuPanel.DEFAULT_MENU_X + rect.x, MainMenuPanel.DEFAULT_MENU_Y + MainMenuPanel.SIZE_PROFILES[1].MENU_HEIGHT + rect.y, rect.width, rect.height); // TODO use current size profile - } - - internal bool IsNodeWithinViewDistance(ushort nodeId) { - bool ret = false; - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - ret = IsPosWithinOverlayDistance(node.m_position); - return true; - }); - return ret; - } - - internal bool IsSegmentWithinViewDistance(ushort segmentId) { - bool ret = false; - Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { - Vector3 centerPos = segment.m_bounds.center; - ret = IsPosWithinOverlayDistance(centerPos); - return true; - }); - return ret; - } - - internal bool IsPosWithinOverlayDistance(Vector3 position) { - return (position - Singleton.instance.m_simulationView.m_position).magnitude <= TrafficManagerTool.MaxOverlayDistance; - } - - internal static float AdaptWidth(float originalWidth) { - return originalWidth; - //return originalWidth * ((float)Screen.width / 1920f); - } - - internal float GetBaseZoom() { - return (float)Screen.height / 1200f; - } - - internal float GetWindowAlpha() { - return TransparencyToAlpha(GlobalConfig.Instance.Main.GuiTransparency); - } - - internal float GetHandleAlpha(bool hovered) { - byte transparency = GlobalConfig.Instance.Main.OverlayTransparency; - if (hovered) { - // reduce transparency when handle is hovered - transparency = (byte)Math.Min(20, transparency >> 2); - } - return TransparencyToAlpha(transparency); - } - - private static float TransparencyToAlpha(byte transparency) { - return Mathf.Clamp(100 - (int)transparency, 0f, 100f) / 100f; - } - - internal void Initialize() { - Log.Info("TrafficManagerTool: Initialization running now."); - subTools.Clear(); - subTools[ToolMode.SwitchTrafficLight] = new ToggleTrafficLightsTool(this); - subTools[ToolMode.AddPrioritySigns] = new PrioritySignsTool(this); - subTools[ToolMode.ManualSwitch] = new ManualTrafficLightsTool(this); - SubTool timedLightsTool = new TimedTrafficLightsTool(this); - subTools[ToolMode.TimedLightsAddNode] = timedLightsTool; - subTools[ToolMode.TimedLightsRemoveNode] = timedLightsTool; - subTools[ToolMode.TimedLightsSelectNode] = timedLightsTool; - subTools[ToolMode.TimedLightsShowLights] = timedLightsTool; - subTools[ToolMode.TimedLightsCopyLights] = timedLightsTool; - subTools[ToolMode.VehicleRestrictions] = new VehicleRestrictionsTool(this); - subTools[ToolMode.SpeedLimits] = new SpeedLimitsTool(this); - subTools[ToolMode.LaneChange] = new LaneArrowTool(this); - subTools[ToolMode.LaneConnector] = new LaneConnectorTool(this); - subTools[ToolMode.JunctionRestrictions] = new JunctionRestrictionsTool(this); - subTools[ToolMode.ParkingRestrictions] = new ParkingRestrictionsTool(this); - - InitializeSubTools(); - - SetToolMode(ToolMode.None); - - if (confDisposable != null) { - confDisposable.Dispose(); - } - confDisposable = GlobalConfig.Instance.Subscribe(this); - - Log.Info("TrafficManagerTool: Initialization completed."); - } - - public void OnUpdate(GlobalConfig config) { - InitializeSubTools(); - } - - internal void InitializeSubTools() { - foreach (KeyValuePair e in subTools) { - e.Value.Initialize(); - } - } - - protected override void Awake() { - Log._Debug($"TrafficLightTool: Awake {this.GetHashCode()}"); - base.Awake(); - } - - public SubTool GetSubTool(ToolMode mode) { - SubTool ret; - if (subTools.TryGetValue(mode, out ret)) { - return ret; - } - return null; - } - - public ToolMode GetToolMode() { - return _toolMode; - } - - public void SetToolMode(ToolMode mode) { - Log._Debug($"SetToolMode: {mode}"); - - bool toolModeChanged = (mode != _toolMode); - var oldToolMode = _toolMode; - SubTool oldSubTool = null; - subTools.TryGetValue(oldToolMode, out oldSubTool); - _toolMode = mode; - if (!subTools.TryGetValue(_toolMode, out activeSubTool)) { - activeSubTool = null; - } - bool realToolChange = toolModeChanged; - - if (oldSubTool != null) { - if ((oldToolMode == ToolMode.TimedLightsSelectNode || oldToolMode == ToolMode.TimedLightsShowLights || oldToolMode == ToolMode.TimedLightsAddNode || oldToolMode == ToolMode.TimedLightsRemoveNode || oldToolMode == ToolMode.TimedLightsCopyLights)) { // TODO refactor to SubToolMode - if (mode != ToolMode.TimedLightsSelectNode && mode != ToolMode.TimedLightsShowLights && mode != ToolMode.TimedLightsAddNode && mode != ToolMode.TimedLightsRemoveNode && mode != ToolMode.TimedLightsCopyLights) { - oldSubTool.Cleanup(); - } - } else { - oldSubTool.Cleanup(); - } - } - - if (toolModeChanged && activeSubTool != null) { - if ((oldToolMode == ToolMode.TimedLightsSelectNode || oldToolMode == ToolMode.TimedLightsShowLights || oldToolMode == ToolMode.TimedLightsAddNode || oldToolMode == ToolMode.TimedLightsRemoveNode || oldToolMode == ToolMode.TimedLightsCopyLights)) { // TODO refactor to SubToolMode - - if (mode != ToolMode.TimedLightsSelectNode && mode != ToolMode.TimedLightsShowLights && mode != ToolMode.TimedLightsAddNode && mode != ToolMode.TimedLightsRemoveNode && mode != ToolMode.TimedLightsCopyLights) { - activeSubTool.Cleanup(); - } else { - realToolChange = false; - } - } else { - activeSubTool.Cleanup(); - } - } - - SelectedNodeId = 0; - SelectedSegmentId = 0; - - //Log._Debug($"Getting activeSubTool for mode {_toolMode} {subTools.Count}"); - - //subTools.TryGetValue((int)_toolMode, out activeSubTool); - //Log._Debug($"activeSubTool is now {activeSubTool}"); - - if (toolModeChanged && activeSubTool != null) { - activeSubTool.OnActivate(); - if (realToolChange) { - ShowAdvisor(activeSubTool.GetTutorialKey()); - } - } - } - - // Overridden to disable base class behavior - protected override void OnEnable() { - Log._Debug($"TrafficManagerTool.OnEnable(): Performing cleanup"); - foreach (KeyValuePair e in subTools) { - e.Value.Cleanup(); - } - } - - // Overridden to disable base class behavior - protected override void OnDisable() { - } - - public override void RenderGeometry(RenderManager.CameraInfo cameraInfo) { - if (HoveredNodeId != 0) { - m_toolController.RenderCollidingNotifications(cameraInfo, 0, 0); - } - } - - /// - /// Renders overlays (node selection, segment selection, etc.) - /// - /// - public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { - //Log._Debug($"RenderOverlay"); - //Log._Debug($"RenderOverlay: {_toolMode} {activeSubTool} {this.GetHashCode()}"); - - if (!this.isActiveAndEnabled) { - return; - } - - if (activeSubTool != null) { - //Log._Debug($"Rendering overlay in {_toolMode}"); - activeSubTool.RenderOverlay(cameraInfo); - } - - foreach (KeyValuePair e in subTools) { - if (e.Key == GetToolMode()) - continue; - e.Value.RenderInfoOverlay(cameraInfo); - } - } - - /// - /// Primarily handles click events on hovered nodes/segments - /// - protected override void OnToolUpdate() { - base.OnToolUpdate(); - //Log._Debug($"OnToolUpdate"); - - if (Input.GetKeyUp(KeyCode.PageDown)) { - InfoManager.instance.SetCurrentMode(InfoManager.InfoMode.Traffic, InfoManager.SubInfoMode.Default); - UIView.library.Hide("TrafficInfoViewPanel"); - } else if (Input.GetKeyUp(KeyCode.PageUp)) - InfoManager.instance.SetCurrentMode(InfoManager.InfoMode.None, InfoManager.SubInfoMode.Default); - - bool primaryMouseClicked = Input.GetMouseButtonDown(0); - bool secondaryMouseClicked = Input.GetMouseButtonDown(1); - - // check if clicked - if (!primaryMouseClicked && !secondaryMouseClicked) - return; - - // check if mouse is inside panel - if (LoadingExtension.BaseUI.GetMenu().containsMouse + private static SubTool activeSubTool = null; + + private static IDisposable confDisposable; + + static TrafficManagerTool() { + + } + + internal ToolController GetToolController() { + return m_toolController; + } + + internal static Rect MoveGUI(Rect rect) { + // x := main menu x + rect.x + // y := main menu y + main menu height + rect.y + return new Rect(MainMenuPanel.DEFAULT_MENU_X + rect.x, MainMenuPanel.DEFAULT_MENU_Y + MainMenuPanel.SIZE_PROFILES[1].MENU_HEIGHT + rect.y, rect.width, rect.height); // TODO use current size profile + } + + internal bool IsNodeWithinViewDistance(ushort nodeId) { + bool ret = false; + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + ret = IsPosWithinOverlayDistance(node.m_position); + return true; + }); + return ret; + } + + internal bool IsSegmentWithinViewDistance(ushort segmentId) { + bool ret = false; + Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { + Vector3 centerPos = segment.m_bounds.center; + ret = IsPosWithinOverlayDistance(centerPos); + return true; + }); + return ret; + } + + internal bool IsPosWithinOverlayDistance(Vector3 position) { + return (position - Singleton.instance.m_simulationView.m_position).magnitude <= TrafficManagerTool.MaxOverlayDistance; + } + + internal static float AdaptWidth(float originalWidth) { + return originalWidth; + //return originalWidth * ((float)Screen.width / 1920f); + } + + internal float GetBaseZoom() { + return (float)Screen.height / 1200f; + } + + internal float GetWindowAlpha() { + return TransparencyToAlpha(GlobalConfig.Instance.Main.GuiTransparency); + } + + internal float GetHandleAlpha(bool hovered) { + byte transparency = GlobalConfig.Instance.Main.OverlayTransparency; + if (hovered) { + // reduce transparency when handle is hovered + transparency = (byte)Math.Min(20, transparency >> 2); + } + return TransparencyToAlpha(transparency); + } + + private static float TransparencyToAlpha(byte transparency) { + return Mathf.Clamp(100 - (int)transparency, 0f, 100f) / 100f; + } + + internal void Initialize() { + Log.Info("TrafficManagerTool: Initialization running now."); + subTools.Clear(); + subTools[ToolMode.SwitchTrafficLight] = new ToggleTrafficLightsTool(this); + subTools[ToolMode.AddPrioritySigns] = new PrioritySignsTool(this); + subTools[ToolMode.ManualSwitch] = new ManualTrafficLightsTool(this); + SubTool timedLightsTool = new TimedTrafficLightsTool(this); + subTools[ToolMode.TimedLightsAddNode] = timedLightsTool; + subTools[ToolMode.TimedLightsRemoveNode] = timedLightsTool; + subTools[ToolMode.TimedLightsSelectNode] = timedLightsTool; + subTools[ToolMode.TimedLightsShowLights] = timedLightsTool; + subTools[ToolMode.TimedLightsCopyLights] = timedLightsTool; + subTools[ToolMode.VehicleRestrictions] = new VehicleRestrictionsTool(this); + subTools[ToolMode.SpeedLimits] = new SpeedLimitsTool(this); + subTools[ToolMode.LaneChange] = new LaneArrowTool(this); + subTools[ToolMode.LaneConnector] = new LaneConnectorTool(this); + subTools[ToolMode.JunctionRestrictions] = new JunctionRestrictionsTool(this); + subTools[ToolMode.ParkingRestrictions] = new ParkingRestrictionsTool(this); + + InitializeSubTools(); + + SetToolMode(ToolMode.None); + + if (confDisposable != null) { + confDisposable.Dispose(); + } + confDisposable = GlobalConfig.Instance.Subscribe(this); + + Log.Info("TrafficManagerTool: Initialization completed."); + } + + public void OnUpdate(GlobalConfig config) { + InitializeSubTools(); + } + + internal void InitializeSubTools() { + foreach (KeyValuePair e in subTools) { + e.Value.Initialize(); + } + } + + protected override void Awake() { + Log._Debug($"TrafficLightTool: Awake {this.GetHashCode()}"); + base.Awake(); + } + + public SubTool GetSubTool(ToolMode mode) { + SubTool ret; + if (subTools.TryGetValue(mode, out ret)) { + return ret; + } + return null; + } + + public ToolMode GetToolMode() { + return _toolMode; + } + + public void SetToolMode(ToolMode mode) { + Log._Debug($"SetToolMode: {mode}"); + + bool toolModeChanged = (mode != _toolMode); + var oldToolMode = _toolMode; + SubTool oldSubTool = null; + subTools.TryGetValue(oldToolMode, out oldSubTool); + _toolMode = mode; + if (!subTools.TryGetValue(_toolMode, out activeSubTool)) { + activeSubTool = null; + } + bool realToolChange = toolModeChanged; + + if (oldSubTool != null) { + if ((oldToolMode == ToolMode.TimedLightsSelectNode || oldToolMode == ToolMode.TimedLightsShowLights || oldToolMode == ToolMode.TimedLightsAddNode || oldToolMode == ToolMode.TimedLightsRemoveNode || oldToolMode == ToolMode.TimedLightsCopyLights)) { // TODO refactor to SubToolMode + if (mode != ToolMode.TimedLightsSelectNode && mode != ToolMode.TimedLightsShowLights && mode != ToolMode.TimedLightsAddNode && mode != ToolMode.TimedLightsRemoveNode && mode != ToolMode.TimedLightsCopyLights) { + oldSubTool.Cleanup(); + } + } else { + oldSubTool.Cleanup(); + } + } + + if (toolModeChanged && activeSubTool != null) { + if ((oldToolMode == ToolMode.TimedLightsSelectNode || oldToolMode == ToolMode.TimedLightsShowLights || oldToolMode == ToolMode.TimedLightsAddNode || oldToolMode == ToolMode.TimedLightsRemoveNode || oldToolMode == ToolMode.TimedLightsCopyLights)) { // TODO refactor to SubToolMode + + if (mode != ToolMode.TimedLightsSelectNode && mode != ToolMode.TimedLightsShowLights && mode != ToolMode.TimedLightsAddNode && mode != ToolMode.TimedLightsRemoveNode && mode != ToolMode.TimedLightsCopyLights) { + activeSubTool.Cleanup(); + } else { + realToolChange = false; + } + } else { + activeSubTool.Cleanup(); + } + } + + SelectedNodeId = 0; + SelectedSegmentId = 0; + + //Log._Debug($"Getting activeSubTool for mode {_toolMode} {subTools.Count}"); + + //subTools.TryGetValue((int)_toolMode, out activeSubTool); + //Log._Debug($"activeSubTool is now {activeSubTool}"); + + if (toolModeChanged && activeSubTool != null) { + activeSubTool.OnActivate(); + if (realToolChange) { + ShowAdvisor(activeSubTool.GetTutorialKey()); + } + } + } + + // Overridden to disable base class behavior + protected override void OnEnable() { + Log._Debug($"TrafficManagerTool.OnEnable(): Performing cleanup"); + foreach (KeyValuePair e in subTools) { + e.Value.Cleanup(); + } + } + + // Overridden to disable base class behavior + protected override void OnDisable() { + } + + public override void RenderGeometry(RenderManager.CameraInfo cameraInfo) { + if (HoveredNodeId != 0) { + m_toolController.RenderCollidingNotifications(cameraInfo, 0, 0); + } + } + + /// + /// Renders overlays (node selection, segment selection, etc.) + /// + /// + public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { + //Log._Debug($"RenderOverlay"); + //Log._Debug($"RenderOverlay: {_toolMode} {activeSubTool} {this.GetHashCode()}"); + + if (!this.isActiveAndEnabled) { + return; + } + + if (activeSubTool != null) { + //Log._Debug($"Rendering overlay in {_toolMode}"); + activeSubTool.RenderOverlay(cameraInfo); + } + + foreach (KeyValuePair e in subTools) { + if (e.Key == GetToolMode()) + continue; + e.Value.RenderInfoOverlay(cameraInfo); + } + } + + /// + /// Primarily handles click events on hovered nodes/segments + /// + protected override void OnToolUpdate() { + base.OnToolUpdate(); + //Log._Debug($"OnToolUpdate"); + + if (Input.GetKeyUp(KeyCode.PageDown)) { + InfoManager.instance.SetCurrentMode(InfoManager.InfoMode.Traffic, InfoManager.SubInfoMode.Default); + UIView.library.Hide("TrafficInfoViewPanel"); + } else if (Input.GetKeyUp(KeyCode.PageUp)) + InfoManager.instance.SetCurrentMode(InfoManager.InfoMode.None, InfoManager.SubInfoMode.Default); + + bool primaryMouseClicked = Input.GetMouseButtonDown(0); + bool secondaryMouseClicked = Input.GetMouseButtonDown(1); + + // check if clicked + if (!primaryMouseClicked && !secondaryMouseClicked) + return; + + // check if mouse is inside panel + if (LoadingExtension.BaseUI.GetMenu().containsMouse #if DEBUG - || LoadingExtension.BaseUI.GetDebugMenu().containsMouse + || LoadingExtension.BaseUI.GetDebugMenu().containsMouse #endif - ) { + ) { #if DEBUG - Log._Debug($"TrafficManagerTool: OnToolUpdate: Menu contains mouse. Ignoring click."); + Log._Debug($"TrafficManagerTool: OnToolUpdate: Menu contains mouse. Ignoring click."); #endif - return; - } + return; + } - if (/*!elementsHovered || (*/activeSubTool != null && activeSubTool.IsCursorInPanel()/*)*/) { + if (/*!elementsHovered || (*/activeSubTool != null && activeSubTool.IsCursorInPanel()/*)*/) { #if DEBUG - Log._Debug($"TrafficManagerTool: OnToolUpdate: Subtool contains mouse. Ignoring click."); + Log._Debug($"TrafficManagerTool: OnToolUpdate: Subtool contains mouse. Ignoring click."); #endif - //Log.Message("inside ui: " + m_toolController.IsInsideUI + " visible: " + Cursor.visible + " in secondary panel: " + _cursorInSecondaryPanel); - return; - } + //Log.Message("inside ui: " + m_toolController.IsInsideUI + " visible: " + Cursor.visible + " in secondary panel: " + _cursorInSecondaryPanel); + return; + } - /*if (HoveredSegmentId == 0 && HoveredNodeId == 0) { - //Log.Message("no hovered segment"); - return; - }*/ + /*if (HoveredSegmentId == 0 && HoveredNodeId == 0) { + //Log.Message("no hovered segment"); + return; + }*/ - if (activeSubTool != null) { - determineHoveredElements(); + if (activeSubTool != null) { + determineHoveredElements(); - if (primaryMouseClicked) - activeSubTool.OnPrimaryClickOverlay(); + if (primaryMouseClicked) + activeSubTool.OnPrimaryClickOverlay(); - if (secondaryMouseClicked) - activeSubTool.OnSecondaryClickOverlay(); - } - } + if (secondaryMouseClicked) + activeSubTool.OnSecondaryClickOverlay(); + } + } - protected override void OnToolGUI(Event e) { - try { - if (!Input.GetMouseButtonDown(0)) { - mouseClickProcessed = false; - } + protected override void OnToolGUI(Event e) { + try { + if (!Input.GetMouseButtonDown(0)) { + mouseClickProcessed = false; + } - if (Options.nodesOverlay) { - _guiSegments(); - _guiNodes(); - } + if (Options.nodesOverlay) { + _guiSegments(); + _guiNodes(); + } //#if DEBUG - if (Options.vehicleOverlay) { - _guiVehicles(); - } - - if (Options.citizenOverlay) { - _guiCitizens(); - } - - if (Options.buildingOverlay) { - _guiBuildings(); - } - //#endif - - foreach (KeyValuePair en in subTools) { - en.Value.ShowGUIOverlay(en.Key, en.Key != GetToolMode()); - } - - var guiColor = GUI.color; - guiColor.a = 1f; - GUI.color = guiColor; - - if (activeSubTool != null) - activeSubTool.OnToolGUI(e); - else - base.OnToolGUI(e); - - } catch (Exception ex) { - Log.Error("GUI Error: " + ex.ToString()); - } - } - - public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, bool warning=false, bool alpha=false) { - DrawNodeCircle(cameraInfo, nodeId, GetToolColor(warning, false), alpha); - } - - public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, Color color, bool alpha = false) { - var segment = Singleton.instance.m_segments.m_buffer[Singleton.instance.m_nodes.m_buffer[nodeId].m_segment0]; - - Vector3 pos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; - float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos); - if (terrainY > pos.y) - pos.y = terrainY; - - Bezier3 bezier; - bezier.a = pos; - bezier.d = pos; - - NetSegment.CalculateMiddlePoints(bezier.a, segment.m_startDirection, bezier.d, segment.m_endDirection, false, false, out bezier.b, out bezier.c); - - DrawOverlayBezier(cameraInfo, bezier, color, alpha); - } - - private void DrawOverlayBezier(RenderManager.CameraInfo cameraInfo, Bezier3 bezier, Color color, bool alpha=false) { - const float width = 8f; // 8 - small roads; 16 - big roads - Singleton.instance.m_drawCallData.m_overlayCalls++; - Singleton.instance.OverlayEffect.DrawBezier(cameraInfo, color, bezier, width * 2f, width, width, -1f, 1280f, false, alpha); - } - - private void DrawOverlayCircle(RenderManager.CameraInfo cameraInfo, Color color, Vector3 position, float width, bool alpha) { - Singleton.instance.m_drawCallData.m_overlayCalls++; - Singleton.instance.OverlayEffect.DrawCircle(cameraInfo, color, position, width, position.y - 100f, position.y + 100f, false, alpha); - } - - public void DrawStaticSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, - float size) { - DrawGenericSquareOverlayGridTexture(texture, camPos, gridOrigin, cellSize, xu, yu, x, y, size, false); - } - - public bool DrawHoverableSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, - float size) { - return DrawGenericSquareOverlayGridTexture(texture, camPos, gridOrigin, cellSize, xu, yu, x, y, size, true); - } - - public bool DrawGenericSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, - float size, bool canHover) { - return DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellSize, cellSize, xu, yu, x, y, size, size, canHover); - } - - public void DrawStaticOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, - float width, float height) { - DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellWidth, cellHeight, xu, yu, x, y, width, height, false); - } - - public bool DrawHoverableOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, - float width, float height) { - return DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellWidth, cellHeight, xu, yu, x, y, width, height, true); - } - - public bool DrawGenericOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, - float width, float height, bool canHover) { - Vector3 worldPos = gridOrigin + cellWidth * (float)x * xu + cellHeight * (float)y * yu; // grid position in game coordinates - return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, canHover); - } - - public void DrawStaticSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size) { - DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, false); - } - - public bool DrawHoverableSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size) { - return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, true); - } - - public bool DrawGenericSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size, bool canHover) { - return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, canHover); - } - - public void DrawStaticOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height) { - DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, false); - } - - public bool DrawHoverableOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height) { - return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, true); - } - - public bool DrawGenericOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height, bool canHover) { - Vector3 screenPos; - if (! WorldToScreenPoint(worldPos, out screenPos)) { - return false; - } - - float zoom = 1.0f / (worldPos - camPos).magnitude * 100f * GetBaseZoom(); - width *= zoom; - height *= zoom; - - Rect boundingBox = new Rect(screenPos.x - width / 2f, screenPos.y - height / 2f, width, height); - - Color guiColor = GUI.color; - - bool hovered = false; - if (canHover) { - hovered = IsMouseOver(boundingBox); - } - guiColor.a = GetHandleAlpha(hovered); - - GUI.color = guiColor; - GUI.DrawTexture(boundingBox, texture); - - return hovered; - } - - /// - /// Transforms a world point into a screen point - /// - /// - /// - /// - public bool WorldToScreenPoint(Vector3 worldPos, out Vector3 screenPos) { - screenPos = Camera.main.WorldToScreenPoint(worldPos); - screenPos.y = Screen.height - screenPos.y; - - return screenPos.z >= 0; - } - - /// - /// Shows a tutorial message. Must be called by a Unity thread. - /// - /// - public static void ShowAdvisor(string localeKey) { - if (! GlobalConfig.Instance.Main.EnableTutorial) { - return; - } - - if (! Translation.HasString(Translation.TUTORIAL_BODY_KEY_PREFIX + localeKey)) { - return; - } - - Log._Debug($"TrafficManagerTool.ShowAdvisor({localeKey}) called."); - TutorialAdvisorPanel tutorialPanel = ToolsModifierControl.advisorPanel; - string key = Translation.TUTORIAL_KEY_PREFIX + localeKey; - if (GlobalConfig.Instance.Main.DisplayedTutorialMessages.Contains(localeKey)) { - tutorialPanel.Refresh(key, "ToolbarIconZoomOutGlobe", string.Empty); - } else { - tutorialPanel.Show(key, "ToolbarIconZoomOutGlobe", string.Empty, 0f); - GlobalConfig.Instance.Main.AddDisplayedTutorialMessage(localeKey); - GlobalConfig.WriteConfig(); - } - } - - public override void SimulationStep() { - base.SimulationStep(); - - /*currentFrame = Singleton.instance.m_currentFrameIndex >> 2; - - string displayToolTipText = tooltipText; - if (displayToolTipText != null) { - if (currentFrame <= tooltipStartFrame + 50) { - ShowToolInfo(true, displayToolTipText, (Vector3)tooltipWorldPos); - } else { - //ShowToolInfo(false, tooltipText, (Vector3)tooltipWorldPos); - //ShowToolInfo(false, null, Vector3.zero); - tooltipStartFrame = 0; - tooltipText = null; - tooltipWorldPos = null; - } - }*/ - - if (GetToolMode() == ToolMode.None) { - ToolCursor = null; - } else { - bool elementsHovered = determineHoveredElements(); - - var netTool = ToolsModifierControl.toolController.Tools.OfType().FirstOrDefault(nt => nt.m_prefab != null); - - if (netTool != null && elementsHovered) { - ToolCursor = netTool.m_upgradeCursor; - } - } - } - - public bool DoRayCast(RaycastInput input, out RaycastOutput output) { - return RayCast(input, out output); - } - - private bool determineHoveredElements() { - var mouseRayValid = !UIView.IsInsideUI() && Cursor.visible && (activeSubTool == null || !activeSubTool.IsCursorInPanel()); - - if (mouseRayValid) { - ushort oldHoveredSegmentId = HoveredSegmentId; - ushort oldHoveredNodeId = HoveredNodeId; - - HoveredSegmentId = 0; - HoveredNodeId = 0; - - // find currently hovered node - var nodeInput = new RaycastInput(this.m_mouseRay, this.m_mouseRayLength); - // find road nodes - nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; - nodeInput.m_netService.m_service = ItemClass.Service.Road; - /*nodeInput.m_netService2.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.PublicTransport | ItemClass.Layer.MetroTunnels; - nodeInput.m_netService2.m_service = ItemClass.Service.PublicTransport; - nodeInput.m_netService2.m_subService = ItemClass.SubService.PublicTransportTrain;*/ - nodeInput.m_ignoreTerrain = true; - nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; - //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; - - RaycastOutput nodeOutput; - if (RayCast(nodeInput, out nodeOutput)) { - HoveredNodeId = nodeOutput.m_netNode; - } else { - // find train nodes - nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; - nodeInput.m_netService.m_service = ItemClass.Service.PublicTransport; - nodeInput.m_netService.m_subService = ItemClass.SubService.PublicTransportTrain; - nodeInput.m_ignoreTerrain = true; - nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; - //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; - - if (RayCast(nodeInput, out nodeOutput)) { - HoveredNodeId = nodeOutput.m_netNode; - } else { - // find metro nodes - nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; - nodeInput.m_netService.m_service = ItemClass.Service.PublicTransport; - nodeInput.m_netService.m_subService = ItemClass.SubService.PublicTransportMetro; - nodeInput.m_ignoreTerrain = true; - nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; - //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; - - if (RayCast(nodeInput, out nodeOutput)) { - HoveredNodeId = nodeOutput.m_netNode; - } - } - } - - // find currently hovered segment - var segmentInput = new RaycastInput(this.m_mouseRay, this.m_mouseRayLength); - // find road segments - segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; - segmentInput.m_netService.m_service = ItemClass.Service.Road; - segmentInput.m_ignoreTerrain = true; - segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; - //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; - - RaycastOutput segmentOutput; - if (RayCast(segmentInput, out segmentOutput)) { - HoveredSegmentId = segmentOutput.m_netSegment; - } else { - // find train segments - segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; - segmentInput.m_netService.m_service = ItemClass.Service.PublicTransport; - segmentInput.m_netService.m_subService = ItemClass.SubService.PublicTransportTrain; - segmentInput.m_ignoreTerrain = true; - segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; - //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; - - if (RayCast(segmentInput, out segmentOutput)) { - HoveredSegmentId = segmentOutput.m_netSegment; - } else { - // find metro segments - segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; - segmentInput.m_netService.m_service = ItemClass.Service.PublicTransport; - segmentInput.m_netService.m_subService = ItemClass.SubService.PublicTransportMetro; - segmentInput.m_ignoreTerrain = true; - segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; - //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; - - if (RayCast(segmentInput, out segmentOutput)) { - HoveredSegmentId = segmentOutput.m_netSegment; - } - } - } - - if (HoveredNodeId <= 0 && HoveredSegmentId > 0) { - // alternative way to get a node hit: check distance to start and end nodes of the segment - ushort startNodeId = Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_startNode; - ushort endNodeId = Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_endNode; - - float startDist = (segmentOutput.m_hitPos - Singleton.instance.m_nodes.m_buffer[startNodeId].m_position).magnitude; - float endDist = (segmentOutput.m_hitPos - Singleton.instance.m_nodes.m_buffer[endNodeId].m_position).magnitude; - if (startDist < endDist && startDist < 75f) - HoveredNodeId = startNodeId; - else if (endDist < startDist && endDist < 75f) - HoveredNodeId = endNodeId; - } - - /*if (oldHoveredNodeId != HoveredNodeId || oldHoveredSegmentId != HoveredSegmentId) { - Log._Debug($"*** Mouse ray @ node {HoveredNodeId}, segment {HoveredSegmentId}, toolMode={GetToolMode()}"); - }*/ + if (Options.vehicleOverlay) { + _guiVehicles(); + } + + if (Options.citizenOverlay) { + _guiCitizens(); + } + + if (Options.buildingOverlay) { + _guiBuildings(); + } + //#endif + + foreach (KeyValuePair en in subTools) { + en.Value.ShowGUIOverlay(en.Key, en.Key != GetToolMode()); + } + + var guiColor = GUI.color; + guiColor.a = 1f; + GUI.color = guiColor; + + if (activeSubTool != null) + activeSubTool.OnToolGUI(e); + else + base.OnToolGUI(e); + + } catch (Exception ex) { + Log.Error("GUI Error: " + ex.ToString()); + } + } + + public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, bool warning=false, bool alpha=false) { + DrawNodeCircle(cameraInfo, nodeId, GetToolColor(warning, false), alpha); + } + + public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, Color color, bool alpha = false) { + var segment = Singleton.instance.m_segments.m_buffer[Singleton.instance.m_nodes.m_buffer[nodeId].m_segment0]; + + Vector3 pos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; + float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos); + if (terrainY > pos.y) + pos.y = terrainY; + + Bezier3 bezier; + bezier.a = pos; + bezier.d = pos; + + NetSegment.CalculateMiddlePoints(bezier.a, segment.m_startDirection, bezier.d, segment.m_endDirection, false, false, out bezier.b, out bezier.c); + + DrawOverlayBezier(cameraInfo, bezier, color, alpha); + } + + private void DrawOverlayBezier(RenderManager.CameraInfo cameraInfo, Bezier3 bezier, Color color, bool alpha=false) { + const float width = 8f; // 8 - small roads; 16 - big roads + Singleton.instance.m_drawCallData.m_overlayCalls++; + Singleton.instance.OverlayEffect.DrawBezier(cameraInfo, color, bezier, width * 2f, width, width, -1f, 1280f, false, alpha); + } + + private void DrawOverlayCircle(RenderManager.CameraInfo cameraInfo, Color color, Vector3 position, float width, bool alpha) { + Singleton.instance.m_drawCallData.m_overlayCalls++; + Singleton.instance.OverlayEffect.DrawCircle(cameraInfo, color, position, width, position.y - 100f, position.y + 100f, false, alpha); + } + + public void DrawStaticSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, + float size) { + DrawGenericSquareOverlayGridTexture(texture, camPos, gridOrigin, cellSize, xu, yu, x, y, size, false); + } + + public bool DrawHoverableSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, + float size) { + return DrawGenericSquareOverlayGridTexture(texture, camPos, gridOrigin, cellSize, xu, yu, x, y, size, true); + } + + public bool DrawGenericSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, + float size, bool canHover) { + return DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellSize, cellSize, xu, yu, x, y, size, size, canHover); + } + + public void DrawStaticOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, + float width, float height) { + DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellWidth, cellHeight, xu, yu, x, y, width, height, false); + } + + public bool DrawHoverableOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, + float width, float height) { + return DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellWidth, cellHeight, xu, yu, x, y, width, height, true); + } + + public bool DrawGenericOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, + float width, float height, bool canHover) { + Vector3 worldPos = gridOrigin + cellWidth * (float)x * xu + cellHeight * (float)y * yu; // grid position in game coordinates + return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, canHover); + } + + public void DrawStaticSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size) { + DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, false); + } + + public bool DrawHoverableSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size) { + return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, true); + } + + public bool DrawGenericSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size, bool canHover) { + return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, canHover); + } + + public void DrawStaticOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height) { + DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, false); + } + + public bool DrawHoverableOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height) { + return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, true); + } + + public bool DrawGenericOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height, bool canHover) { + Vector3 screenPos; + if (! WorldToScreenPoint(worldPos, out screenPos)) { + return false; + } + + float zoom = 1.0f / (worldPos - camPos).magnitude * 100f * GetBaseZoom(); + width *= zoom; + height *= zoom; + + Rect boundingBox = new Rect(screenPos.x - width / 2f, screenPos.y - height / 2f, width, height); + + Color guiColor = GUI.color; + + bool hovered = false; + if (canHover) { + hovered = IsMouseOver(boundingBox); + } + guiColor.a = GetHandleAlpha(hovered); + + GUI.color = guiColor; + GUI.DrawTexture(boundingBox, texture); + + return hovered; + } + + /// + /// Transforms a world point into a screen point + /// + /// + /// + /// + public bool WorldToScreenPoint(Vector3 worldPos, out Vector3 screenPos) { + screenPos = Camera.main.WorldToScreenPoint(worldPos); + screenPos.y = Screen.height - screenPos.y; + + return screenPos.z >= 0; + } + + /// + /// Shows a tutorial message. Must be called by a Unity thread. + /// + /// + public static void ShowAdvisor(string localeKey) { + if (! GlobalConfig.Instance.Main.EnableTutorial) { + return; + } + + if (! Translation.HasString(Translation.TUTORIAL_BODY_KEY_PREFIX + localeKey)) { + return; + } - return (HoveredNodeId != 0 || HoveredSegmentId != 0); - } else { - //Log._Debug($"Mouse ray invalid: {UIView.IsInsideUI()} {Cursor.visible} {activeSubTool == null} {activeSubTool.IsCursorInPanel()}"); + Log._Debug($"TrafficManagerTool.ShowAdvisor({localeKey}) called."); + TutorialAdvisorPanel tutorialPanel = ToolsModifierControl.advisorPanel; + string key = Translation.TUTORIAL_KEY_PREFIX + localeKey; + if (GlobalConfig.Instance.Main.DisplayedTutorialMessages.Contains(localeKey)) { + tutorialPanel.Refresh(key, "ToolbarIconZoomOutGlobe", string.Empty); + } else { + tutorialPanel.Show(key, "ToolbarIconZoomOutGlobe", string.Empty, 0f); + GlobalConfig.Instance.Main.AddDisplayedTutorialMessage(localeKey); + GlobalConfig.WriteConfig(); } + } + + public override void SimulationStep() { + base.SimulationStep(); + + /*currentFrame = Singleton.instance.m_currentFrameIndex >> 2; + + string displayToolTipText = tooltipText; + if (displayToolTipText != null) { + if (currentFrame <= tooltipStartFrame + 50) { + ShowToolInfo(true, displayToolTipText, (Vector3)tooltipWorldPos); + } else { + //ShowToolInfo(false, tooltipText, (Vector3)tooltipWorldPos); + //ShowToolInfo(false, null, Vector3.zero); + tooltipStartFrame = 0; + tooltipText = null; + tooltipWorldPos = null; + } + }*/ + + if (GetToolMode() == ToolMode.None) { + ToolCursor = null; + } else { + bool elementsHovered = determineHoveredElements(); + + var netTool = ToolsModifierControl.toolController.Tools.OfType().FirstOrDefault(nt => nt.m_prefab != null); + + if (netTool != null && elementsHovered) { + ToolCursor = netTool.m_upgradeCursor; + } + } + } + + public bool DoRayCast(RaycastInput input, out RaycastOutput output) { + return RayCast(input, out output); + } + + private bool determineHoveredElements() { + var mouseRayValid = !UIView.IsInsideUI() && Cursor.visible && (activeSubTool == null || !activeSubTool.IsCursorInPanel()); + + if (mouseRayValid) { + ushort oldHoveredSegmentId = HoveredSegmentId; + ushort oldHoveredNodeId = HoveredNodeId; + + HoveredSegmentId = 0; + HoveredNodeId = 0; + + // find currently hovered node + var nodeInput = new RaycastInput(this.m_mouseRay, this.m_mouseRayLength); + // find road nodes + nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; + nodeInput.m_netService.m_service = ItemClass.Service.Road; + /*nodeInput.m_netService2.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.PublicTransport | ItemClass.Layer.MetroTunnels; + nodeInput.m_netService2.m_service = ItemClass.Service.PublicTransport; + nodeInput.m_netService2.m_subService = ItemClass.SubService.PublicTransportTrain;*/ + nodeInput.m_ignoreTerrain = true; + nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; + //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; + + RaycastOutput nodeOutput; + if (RayCast(nodeInput, out nodeOutput)) { + HoveredNodeId = nodeOutput.m_netNode; + } else { + // find train nodes + nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; + nodeInput.m_netService.m_service = ItemClass.Service.PublicTransport; + nodeInput.m_netService.m_subService = ItemClass.SubService.PublicTransportTrain; + nodeInput.m_ignoreTerrain = true; + nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; + //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; + + if (RayCast(nodeInput, out nodeOutput)) { + HoveredNodeId = nodeOutput.m_netNode; + } else { + // find metro nodes + nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; + nodeInput.m_netService.m_service = ItemClass.Service.PublicTransport; + nodeInput.m_netService.m_subService = ItemClass.SubService.PublicTransportMetro; + nodeInput.m_ignoreTerrain = true; + nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; + //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; + + if (RayCast(nodeInput, out nodeOutput)) { + HoveredNodeId = nodeOutput.m_netNode; + } + } + } + + // find currently hovered segment + var segmentInput = new RaycastInput(this.m_mouseRay, this.m_mouseRayLength); + // find road segments + segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; + segmentInput.m_netService.m_service = ItemClass.Service.Road; + segmentInput.m_ignoreTerrain = true; + segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; + //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; + + RaycastOutput segmentOutput; + if (RayCast(segmentInput, out segmentOutput)) { + HoveredSegmentId = segmentOutput.m_netSegment; + } else { + // find train segments + segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; + segmentInput.m_netService.m_service = ItemClass.Service.PublicTransport; + segmentInput.m_netService.m_subService = ItemClass.SubService.PublicTransportTrain; + segmentInput.m_ignoreTerrain = true; + segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; + //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; + + if (RayCast(segmentInput, out segmentOutput)) { + HoveredSegmentId = segmentOutput.m_netSegment; + } else { + // find metro segments + segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; + segmentInput.m_netService.m_service = ItemClass.Service.PublicTransport; + segmentInput.m_netService.m_subService = ItemClass.SubService.PublicTransportMetro; + segmentInput.m_ignoreTerrain = true; + segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; + //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; + + if (RayCast(segmentInput, out segmentOutput)) { + HoveredSegmentId = segmentOutput.m_netSegment; + } + } + } + + if (HoveredNodeId <= 0 && HoveredSegmentId > 0) { + // alternative way to get a node hit: check distance to start and end nodes of the segment + ushort startNodeId = Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_startNode; + ushort endNodeId = Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_endNode; + + float startDist = (segmentOutput.m_hitPos - Singleton.instance.m_nodes.m_buffer[startNodeId].m_position).magnitude; + float endDist = (segmentOutput.m_hitPos - Singleton.instance.m_nodes.m_buffer[endNodeId].m_position).magnitude; + if (startDist < endDist && startDist < 75f) + HoveredNodeId = startNodeId; + else if (endDist < startDist && endDist < 75f) + HoveredNodeId = endNodeId; + } + + /*if (oldHoveredNodeId != HoveredNodeId || oldHoveredSegmentId != HoveredSegmentId) { + Log._Debug($"*** Mouse ray @ node {HoveredNodeId}, segment {HoveredSegmentId}, toolMode={GetToolMode()}"); +}*/ + + return (HoveredNodeId != 0 || HoveredSegmentId != 0); + } else { + //Log._Debug($"Mouse ray invalid: {UIView.IsInsideUI()} {Cursor.visible} {activeSubTool == null} {activeSubTool.IsCursorInPanel()}"); + } + + return mouseRayValid; + } + + /// + /// Displays lane ids over lanes + /// + private void _guiLanes(ushort segmentId, ref NetSegment segment, ref NetInfo segmentInfo) { + GUIStyle _counterStyle = new GUIStyle(); + Vector3 centerPos = segment.m_bounds.center; + Vector3 screenPos; + bool visible = WorldToScreenPoint(centerPos, out screenPos); + + if (! visible) { + return; + } + + screenPos.y -= 200; + + if (screenPos.z < 0) + return; - return mouseRayValid; - } - - /// - /// Displays lane ids over lanes - /// - private void _guiLanes(ushort segmentId, ref NetSegment segment, ref NetInfo segmentInfo) { - GUIStyle _counterStyle = new GUIStyle(); - Vector3 centerPos = segment.m_bounds.center; - Vector3 screenPos; - bool visible = WorldToScreenPoint(centerPos, out screenPos); - - if (! visible) { - return; - } - - screenPos.y -= 200; - - if (screenPos.z < 0) - return; - - var camPos = Singleton.instance.m_simulationView.m_position; - var diff = centerPos - camPos; - if (diff.magnitude > DebugCloseLod) - return; // do not draw if too distant - - var zoom = 1.0f / diff.magnitude * 150f; - - _counterStyle.fontSize = (int)(11f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 0f); - - /*uint totalDensity = 0u; - for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { - if (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length) - totalDensity += CustomRoadAI.currentLaneDensities[segmentId][i]; - }*/ - - uint curLaneId = segment.m_lanes; - String labelStr = ""; - for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { - if (curLaneId == 0) - break; - - TrafficMeasurementManager.LaneTrafficData laneTrafficData; - bool laneTrafficDataLoaded = TrafficMeasurementManager.Instance.GetLaneTrafficData(segmentId, (byte)i, out laneTrafficData); - - NetInfo.Lane laneInfo = segmentInfo.m_lanes[i]; + var camPos = Singleton.instance.m_simulationView.m_position; + var diff = centerPos - camPos; + if (diff.magnitude > DebugCloseLod) + return; // do not draw if too distant + + var zoom = 1.0f / diff.magnitude * 150f; + + _counterStyle.fontSize = (int)(11f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 0f); + + /*uint totalDensity = 0u; + for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { + if (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length) + totalDensity += CustomRoadAI.currentLaneDensities[segmentId][i]; + }*/ + + uint curLaneId = segment.m_lanes; + String labelStr = ""; + for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { + if (curLaneId == 0) + break; + + TrafficMeasurementManager.LaneTrafficData laneTrafficData; + bool laneTrafficDataLoaded = TrafficMeasurementManager.Instance.GetLaneTrafficData(segmentId, (byte)i, out laneTrafficData); + + NetInfo.Lane laneInfo = segmentInfo.m_lanes[i]; #if PFTRAFFICSTATS uint pfTrafficBuf = TrafficMeasurementManager.Instance.segmentDirTrafficData[TrafficMeasurementManager.Instance.GetDirIndex(segmentId, laneInfo.m_finalDirection)].totalPathFindTrafficBuffer; #endif - //TrafficMeasurementManager.Instance.GetTrafficData(segmentId, laneInfo.m_finalDirection, out dirTrafficData); + //TrafficMeasurementManager.Instance.GetTrafficData(segmentId, laneInfo.m_finalDirection, out dirTrafficData); - //int dirIndex = laneInfo.m_finalDirection == NetInfo.Direction.Backward ? 1 : 0; + //int dirIndex = laneInfo.m_finalDirection == NetInfo.Direction.Backward ? 1 : 0; - labelStr += "L idx " + i + ", id " + curLaneId; + labelStr += "L idx " + i + ", id " + curLaneId; #if DEBUG - labelStr += ", in: " + RoutingManager.Instance.CalcInnerSimilarLaneIndex(segmentId, i) + ", out: " + RoutingManager.Instance.CalcOuterSimilarLaneIndex(segmentId, i) + ", f: " + ((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[curLaneId].m_flags).ToString() + ", l: " + SpeedLimitManager.Instance.GetCustomSpeedLimit(curLaneId) + " km/h, rst: " + VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)i, laneInfo, VehicleRestrictionsMode.Configured) + ", dir: " + laneInfo.m_direction + ", fnl: " + laneInfo.m_finalDirection + ", pos: " + String.Format("{0:0.##}", laneInfo.m_position) + ", sim: " + laneInfo.m_similarLaneIndex + " for " + laneInfo.m_vehicleType + "/" + laneInfo.m_laneType; + labelStr += ", in: " + RoutingManager.Instance.CalcInnerSimilarLaneIndex(segmentId, i) + ", out: " + RoutingManager.Instance.CalcOuterSimilarLaneIndex(segmentId, i) + ", f: " + ((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[curLaneId].m_flags).ToString() + ", l: " + SpeedLimitManager.Instance.GetCustomSpeedLimit(curLaneId) + " km/h, rst: " + VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)i, laneInfo, VehicleRestrictionsMode.Configured) + ", dir: " + laneInfo.m_direction + ", fnl: " + laneInfo.m_finalDirection + ", pos: " + String.Format("{0:0.##}", laneInfo.m_position) + ", sim: " + laneInfo.m_similarLaneIndex + " for " + laneInfo.m_vehicleType + "/" + laneInfo.m_laneType; #endif - if (laneTrafficDataLoaded) { - labelStr += ", sp: " + (TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(segmentId, (byte)i, curLaneId, laneInfo) / 100) + "%"; + if (laneTrafficDataLoaded) { + labelStr += ", sp: " + (TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(segmentId, (byte)i, curLaneId, laneInfo) / 100) + "%"; #if DEBUG - labelStr += ", buf: " + laneTrafficData.trafficBuffer + ", max: " + laneTrafficData.maxTrafficBuffer + ", acc: " + laneTrafficData.accumulatedSpeeds; + labelStr += ", buf: " + laneTrafficData.trafficBuffer + ", max: " + laneTrafficData.maxTrafficBuffer + ", acc: " + laneTrafficData.accumulatedSpeeds; #if PFTRAFFICSTATS labelStr += ", pfBuf: " + laneTrafficData.pathFindTrafficBuffer + "/" + laneTrafficData.lastPathFindTrafficBuffer + ", (" + (pfTrafficBuf > 0 ? "" + ((laneTrafficData.lastPathFindTrafficBuffer * 100u) / pfTrafficBuf) : "n/a") + " %)"; #endif @@ -739,91 +735,91 @@ private void _guiLanes(ushort segmentId, ref NetSegment segment, ref NetInfo seg } labelStr += ", acc: " + laneTrafficData[i].accumulatedDensities; #endif - } + } - labelStr += ", nd: " + Singleton.instance.m_lanes.m_buffer[curLaneId].m_nodes; + labelStr += ", nd: " + Singleton.instance.m_lanes.m_buffer[curLaneId].m_nodes; #if DEBUG - //labelStr += " (" + (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length ? "" + CustomRoadAI.currentLaneDensities[segmentId][i] : "?") + "/" + (CustomRoadAI.maxLaneDensities[segmentId] != null && i < CustomRoadAI.maxLaneDensities[segmentId].Length ? "" + CustomRoadAI.maxLaneDensities[segmentId][i] : "?") + "/" + totalDensity + ")"; - //labelStr += " (" + (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length ? "" + CustomRoadAI.currentLaneDensities[segmentId][i] : "?") + "/" + totalDensity + ")"; + //labelStr += " (" + (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length ? "" + CustomRoadAI.currentLaneDensities[segmentId][i] : "?") + "/" + (CustomRoadAI.maxLaneDensities[segmentId] != null && i < CustomRoadAI.maxLaneDensities[segmentId].Length ? "" + CustomRoadAI.maxLaneDensities[segmentId][i] : "?") + "/" + totalDensity + ")"; + //labelStr += " (" + (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length ? "" + CustomRoadAI.currentLaneDensities[segmentId][i] : "?") + "/" + totalDensity + ")"; #endif - //labelStr += ", abs. dens.: " + (CustomRoadAI.laneMeanAbsDensities[segmentId] != null && i < CustomRoadAI.laneMeanAbsDensities[segmentId].Length ? "" + CustomRoadAI.laneMeanAbsDensities[segmentId][i] : "?") + " %"; - labelStr += "\n"; + //labelStr += ", abs. dens.: " + (CustomRoadAI.laneMeanAbsDensities[segmentId] != null && i < CustomRoadAI.laneMeanAbsDensities[segmentId].Length ? "" + CustomRoadAI.laneMeanAbsDensities[segmentId][i] : "?") + " %"; + labelStr += "\n"; - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - } - - Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); - Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); - - GUI.Label(labelRect, labelStr, _counterStyle); - } - - /// - /// Displays segment ids over segments - /// - private void _guiSegments() { - TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance; - - GUIStyle _counterStyle = new GUIStyle(); - SegmentEndManager endMan = SegmentEndManager.Instance; - Array16 segments = Singleton.instance.m_segments; - for (int i = 1; i < segments.m_size; ++i) { - if (segments.m_buffer[i].m_flags == NetSegment.Flags.None) // segment is unused - continue; - ItemClass.Service service = segments.m_buffer[i].Info.GetService(); - ItemClass.SubService subService = segments.m_buffer[i].Info.GetSubService(); - /*if (service != ItemClass.Service.Road) { - if (service != ItemClass.Service.PublicTransport) { - continue; - } else { - if (subService != ItemClass.SubService.PublicTransportBus && subService != ItemClass.SubService.PublicTransportCableCar && - subService != ItemClass.SubService.PublicTransportMetro && subService != ItemClass.SubService.PublicTransportMonorail && - subService != ItemClass.SubService.PublicTransportTrain) { - continue; - } - } - }*/ + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + } + + Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); + Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); + + GUI.Label(labelRect, labelStr, _counterStyle); + } + + /// + /// Displays segment ids over segments + /// + private void _guiSegments() { + TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance; + + GUIStyle _counterStyle = new GUIStyle(); + SegmentEndManager endMan = SegmentEndManager.Instance; + Array16 segments = Singleton.instance.m_segments; + for (int i = 1; i < segments.m_size; ++i) { + if (segments.m_buffer[i].m_flags == NetSegment.Flags.None) // segment is unused + continue; + ItemClass.Service service = segments.m_buffer[i].Info.GetService(); + ItemClass.SubService subService = segments.m_buffer[i].Info.GetSubService(); + /*if (service != ItemClass.Service.Road) { + if (service != ItemClass.Service.PublicTransport) { + continue; + } else { + if (subService != ItemClass.SubService.PublicTransportBus && subService != ItemClass.SubService.PublicTransportCableCar && + subService != ItemClass.SubService.PublicTransportMetro && subService != ItemClass.SubService.PublicTransportMonorail && + subService != ItemClass.SubService.PublicTransportTrain) { + continue; + } + } + }*/ #if !DEBUG if ((segments.m_buffer[i].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) continue; #endif - var segmentInfo = segments.m_buffer[i].Info; + var segmentInfo = segments.m_buffer[i].Info; - Vector3 centerPos = segments.m_buffer[i].m_bounds.center; - Vector3 screenPos; - bool visible = WorldToScreenPoint(centerPos, out screenPos); + Vector3 centerPos = segments.m_buffer[i].m_bounds.center; + Vector3 screenPos; + bool visible = WorldToScreenPoint(centerPos, out screenPos); - if (! visible) - continue; + if (! visible) + continue; - var camPos = Singleton.instance.m_simulationView.m_position; - var diff = centerPos - camPos; - if (diff.magnitude > DebugCloseLod) - continue; // do not draw if too distant - - var zoom = 1.0f / diff.magnitude * 150f; + var camPos = Singleton.instance.m_simulationView.m_position; + var diff = centerPos - camPos; + if (diff.magnitude > DebugCloseLod) + continue; // do not draw if too distant - _counterStyle.fontSize = (int)(12f * zoom); - _counterStyle.normal.textColor = new Color(1f, 0f, 0f); + var zoom = 1.0f / diff.magnitude * 150f; - String labelStr = "Segment " + i; + _counterStyle.fontSize = (int)(12f * zoom); + _counterStyle.normal.textColor = new Color(1f, 0f, 0f); + + String labelStr = "Segment " + i; #if DEBUG - labelStr += ", flags: " + segments.m_buffer[i].m_flags.ToString(); // + ", condition: " + segments.m_buffer[i].m_condition; + labelStr += ", flags: " + segments.m_buffer[i].m_flags.ToString(); // + ", condition: " + segments.m_buffer[i].m_condition; #endif #if DEBUG - labelStr += "\nsvc: " + service + ", sub: " + subService; - ISegmentEnd startEnd = endMan.GetSegmentEnd((ushort)i, true); - ISegmentEnd endEnd = endMan.GetSegmentEnd((ushort)i, false); - labelStr += "\nstart? " + (startEnd != null) + " veh.: " + startEnd?.GetRegisteredVehicleCount() + ", end? " + (endEnd != null) + " veh.: " + endEnd?.GetRegisteredVehicleCount(); + labelStr += "\nsvc: " + service + ", sub: " + subService; + ISegmentEnd startEnd = endMan.GetSegmentEnd((ushort)i, true); + ISegmentEnd endEnd = endMan.GetSegmentEnd((ushort)i, false); + labelStr += "\nstart? " + (startEnd != null) + " veh.: " + startEnd?.GetRegisteredVehicleCount() + ", end? " + (endEnd != null) + " veh.: " + endEnd?.GetRegisteredVehicleCount(); #endif - labelStr += "\nTraffic: " + segments.m_buffer[i].m_trafficDensity + " %"; + labelStr += "\nTraffic: " + segments.m_buffer[i].m_trafficDensity + " %"; #if DEBUG - int fwdSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Forward); - int backSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Backward); + int fwdSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Forward); + int backSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Backward); - labelStr += "\n"; + labelStr += "\n"; #if MEASURECONGESTION float fwdCongestionRatio = trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements > 0 ? ((uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested * 100u) / (uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements : 0; // now in % float backCongestionRatio = trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements > 0 ? ((uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested * 100u) / (uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements : 0; // now in % @@ -833,8 +829,8 @@ private void _guiSegments() { labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].minSpeed / 100) + "%/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].minSpeed / 100) + "%"; labelStr += ", "; #endif - labelStr += "mean speeds: "; - labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].meanSpeed / 100) + "%/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].meanSpeed / 100) + "%"; + labelStr += "mean speeds: "; + labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].meanSpeed / 100) + "%/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].meanSpeed / 100) + "%"; #if PFTRAFFICSTATS || MEASURECONGESTION labelStr += "\n"; #endif @@ -849,363 +845,363 @@ private void _guiSegments() { labelStr += "cong: "; labelStr += " " + fwdCongestionRatio + "% (" + trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested + "/" + trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements + ")/" + backCongestionRatio + "% (" + trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested + "/" + trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements + ")"; #endif - labelStr += "\nstart: " + segments.m_buffer[i].m_startNode + ", end: " + segments.m_buffer[i].m_endNode; + labelStr += "\nstart: " + segments.m_buffer[i].m_startNode + ", end: " + segments.m_buffer[i].m_endNode; #endif - Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); - Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); - - GUI.Label(labelRect, labelStr, _counterStyle); - - if (Options.showLanes) - _guiLanes((ushort)i, ref segments.m_buffer[i], ref segmentInfo); - } - } - - /// - /// Displays node ids over nodes - /// - private void _guiNodes() { - GUIStyle _counterStyle = new GUIStyle(); - Array16 nodes = Singleton.instance.m_nodes; - - for (int i = 1; i < nodes.m_size; ++i) { - if ((nodes.m_buffer[i].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) // node is unused - continue; + Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); + Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); - Vector3 pos = nodes.m_buffer[i].m_position; - Vector3 screenPos; - bool visible = WorldToScreenPoint(pos, out screenPos); - - if (! visible) - continue; + GUI.Label(labelRect, labelStr, _counterStyle); - var camPos = Singleton.instance.m_simulationView.m_position; - var diff = pos - camPos; - if (diff.magnitude > DebugCloseLod) - continue; // do not draw if too distant - - var zoom = 1.0f / diff.magnitude * 150f; + if (Options.showLanes) + _guiLanes((ushort)i, ref segments.m_buffer[i], ref segmentInfo); + } + } + + /// + /// Displays node ids over nodes + /// + private void _guiNodes() { + GUIStyle _counterStyle = new GUIStyle(); + Array16 nodes = Singleton.instance.m_nodes; + + for (int i = 1; i < nodes.m_size; ++i) { + if ((nodes.m_buffer[i].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) // node is unused + continue; + + Vector3 pos = nodes.m_buffer[i].m_position; + Vector3 screenPos; + bool visible = WorldToScreenPoint(pos, out screenPos); - _counterStyle.fontSize = (int)(15f * zoom); - _counterStyle.normal.textColor = new Color(0f, 0f, 1f); + if (! visible) + continue; - String labelStr = "Node " + i; + var camPos = Singleton.instance.m_simulationView.m_position; + var diff = pos - camPos; + if (diff.magnitude > DebugCloseLod) + continue; // do not draw if too distant + + var zoom = 1.0f / diff.magnitude * 150f; + + _counterStyle.fontSize = (int)(15f * zoom); + _counterStyle.normal.textColor = new Color(0f, 0f, 1f); + + String labelStr = "Node " + i; #if DEBUG - labelStr += $"\nflags: {nodes.m_buffer[i].m_flags}"; - labelStr += $"\nlane: {nodes.m_buffer[i].m_lane}"; + labelStr += $"\nflags: {nodes.m_buffer[i].m_flags}"; + labelStr += $"\nlane: {nodes.m_buffer[i].m_lane}"; #endif - Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); - Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); - - GUI.Label(labelRect, labelStr, _counterStyle); - } - } - - /// - /// Displays vehicle ids over vehicles - /// - private void _guiVehicles() { - GUIStyle _counterStyle = new GUIStyle(); - Array16 vehicles = Singleton.instance.m_vehicles; - LaneConnectionManager connManager = LaneConnectionManager.Instance; - SimulationManager simManager = Singleton.instance; - NetManager netManager = Singleton.instance; - VehicleStateManager vehStateManager = VehicleStateManager.Instance; - - - int startVehicleId = 1; - int endVehicleId = (int)(vehicles.m_size - 1); + Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); + Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); + + GUI.Label(labelRect, labelStr, _counterStyle); + } + } + + /// + /// Displays vehicle ids over vehicles + /// + private void _guiVehicles() { + GUIStyle _counterStyle = new GUIStyle(); + Array16 vehicles = Singleton.instance.m_vehicles; + LaneConnectionManager connManager = LaneConnectionManager.Instance; + SimulationManager simManager = Singleton.instance; + NetManager netManager = Singleton.instance; + VehicleStateManager vehStateManager = VehicleStateManager.Instance; + + + int startVehicleId = 1; + int endVehicleId = (int)(vehicles.m_size - 1); #if DEBUG - if (GlobalConfig.Instance.Debug.VehicleId != 0) { - startVehicleId = endVehicleId = GlobalConfig.Instance.Debug.VehicleId; - } + if (GlobalConfig.Instance.Debug.VehicleId != 0) { + startVehicleId = endVehicleId = GlobalConfig.Instance.Debug.VehicleId; + } #endif - for (int i = startVehicleId; i <= endVehicleId; ++i) { - Vehicle vehicle = vehicles.m_buffer[i]; - if (vehicle.m_flags == 0) // node is unused - continue; + for (int i = startVehicleId; i <= endVehicleId; ++i) { + Vehicle vehicle = vehicles.m_buffer[i]; + if (vehicle.m_flags == 0) // node is unused + continue; - Vector3 vehPos = vehicle.GetSmoothPosition((ushort)i); - Vector3 screenPos; - bool visible = WorldToScreenPoint(vehPos, out screenPos); - - if (! visible) - continue; + Vector3 vehPos = vehicle.GetSmoothPosition((ushort)i); + Vector3 screenPos; + bool visible = WorldToScreenPoint(vehPos, out screenPos); - var camPos = simManager.m_simulationView.m_position; - var diff = vehPos - camPos; - if (diff.magnitude > DebugCloseLod) - continue; // do not draw if too distant + if (! visible) + continue; - var zoom = 1.0f / diff.magnitude * 150f; + var camPos = simManager.m_simulationView.m_position; + var diff = vehPos - camPos; + if (diff.magnitude > DebugCloseLod) + continue; // do not draw if too distant - _counterStyle.fontSize = (int)(10f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); + var zoom = 1.0f / diff.magnitude * 150f; - VehicleState vState = vehStateManager.VehicleStates[(ushort)i]; - ExtCitizenInstance driverInst = ExtCitizenInstanceManager.Instance.ExtInstances[CustomPassengerCarAI.GetDriverInstanceId((ushort)i, ref Singleton.instance.m_vehicles.m_buffer[i])]; - bool startNode = vState.currentStartNode; - ushort segmentId = vState.currentSegmentId; + _counterStyle.fontSize = (int)(10f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); - // Some magical constant converting magnitudes into km/h - float vehSpeed = SpeedLimit.ToKmphPrecise(vehicle.GetLastFrameVelocity().magnitude / 8f); + VehicleState vState = vehStateManager.VehicleStates[(ushort)i]; + ExtCitizenInstance driverInst = ExtCitizenInstanceManager.Instance.ExtInstances[CustomPassengerCarAI.GetDriverInstanceId((ushort)i, ref Singleton.instance.m_vehicles.m_buffer[i])]; + bool startNode = vState.currentStartNode; + ushort segmentId = vState.currentSegmentId; + + // Some magical constant converting magnitudes into km/h + float vehSpeed = SpeedLimit.ToKmphPrecise(vehicle.GetLastFrameVelocity().magnitude / 8f); #if DEBUG - if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { - continue; - } + if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { + continue; + } #endif - var labelStr = - $"V #{i} is a {(vState.recklessDriver ? "reckless " : string.Empty)}{vState.flags} " + - $"{vState.vehicleType} @ ~{vehSpeed:0.0} km/h (len: {vState.totalLength:0.0}, " + - $"{vState.JunctionTransitState} @ {vState.currentSegmentId} " + - $"({vState.currentStartNode}), l. {vState.currentLaneIndex} -> {vState.nextSegmentId}, " + - $"l. {vState.nextLaneIndex}), w: {vState.waitTime}\n" + - $"di: {driverInst.instanceId} dc: {driverInst.GetCitizenId()} m: {driverInst.pathMode} " + - $"f: {driverInst.failedParkingAttempts} l: {driverInst.parkingSpaceLocation} " + - $"lid: {driverInst.parkingSpaceLocationId} ltsu: {vState.lastTransitStateUpdate} " + - $"lpu: {vState.lastPositionUpdate} als: {vState.lastAltLaneSelSegmentId} " + - $"srnd: {Constants.ManagerFactory.VehicleBehaviorManager.GetStaticVehicleRand((ushort) i)} " + - $"trnd: {Constants.ManagerFactory.VehicleBehaviorManager.GetTimedVehicleRand((ushort) i)}"; - - Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); - Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); - - GUI.Box(labelRect, labelStr, _counterStyle); - - //_counterStyle.normal.background = null; - } - } - - private void _guiCitizens() { - GUIStyle _counterStyle = new GUIStyle(); - Array16 citizenInstances = Singleton.instance.m_instances; - for (int i = 1; i < citizenInstances.m_size; ++i) { - CitizenInstance citizenInstance = citizenInstances.m_buffer[i]; - if (citizenInstance.m_flags == CitizenInstance.Flags.None) - continue; - if ((citizenInstance.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) - continue; + var labelStr = + $"V #{i} is a {(vState.recklessDriver ? "reckless " : string.Empty)}{vState.flags} " + + $"{vState.vehicleType} @ ~{vehSpeed:0.0} km/h (len: {vState.totalLength:0.0}, " + + $"{vState.JunctionTransitState} @ {vState.currentSegmentId} " + + $"({vState.currentStartNode}), l. {vState.currentLaneIndex} -> {vState.nextSegmentId}, " + + $"l. {vState.nextLaneIndex}), w: {vState.waitTime}\n" + + $"di: {driverInst.instanceId} dc: {driverInst.GetCitizenId()} m: {driverInst.pathMode} " + + $"f: {driverInst.failedParkingAttempts} l: {driverInst.parkingSpaceLocation} " + + $"lid: {driverInst.parkingSpaceLocationId} ltsu: {vState.lastTransitStateUpdate} " + + $"lpu: {vState.lastPositionUpdate} als: {vState.lastAltLaneSelSegmentId} " + + $"srnd: {Constants.ManagerFactory.VehicleBehaviorManager.GetStaticVehicleRand((ushort) i)} " + + $"trnd: {Constants.ManagerFactory.VehicleBehaviorManager.GetTimedVehicleRand((ushort) i)}"; + + Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); + Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); + + GUI.Box(labelRect, labelStr, _counterStyle); + + //_counterStyle.normal.background = null; + } + } + + private void _guiCitizens() { + GUIStyle _counterStyle = new GUIStyle(); + Array16 citizenInstances = Singleton.instance.m_instances; + for (int i = 1; i < citizenInstances.m_size; ++i) { + CitizenInstance citizenInstance = citizenInstances.m_buffer[i]; + if (citizenInstance.m_flags == CitizenInstance.Flags.None) + continue; + if ((citizenInstance.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) + continue; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[14]) { + if (GlobalConfig.Instance.Debug.Switches[14]) { #endif - if (citizenInstance.m_path != 0) { - continue; - } + if (citizenInstance.m_path != 0) { + continue; + } #if DEBUG - } + } #endif - Vector3 pos = citizenInstance.GetSmoothPosition((ushort)i); - Vector3 screenPos; - bool visible = WorldToScreenPoint(pos, out screenPos); - - if (! visible) - continue; + Vector3 pos = citizenInstance.GetSmoothPosition((ushort)i); + Vector3 screenPos; + bool visible = WorldToScreenPoint(pos, out screenPos); + + if (! visible) + continue; - var camPos = Singleton.instance.m_simulationView.m_position; - var diff = pos - camPos; - if (diff.magnitude > DebugCloseLod) - continue; // do not draw if too distant + var camPos = Singleton.instance.m_simulationView.m_position; + var diff = pos - camPos; + if (diff.magnitude > DebugCloseLod) + continue; // do not draw if too distant - var zoom = 1.0f / diff.magnitude * 150f; + var zoom = 1.0f / diff.magnitude * 150f; - _counterStyle.fontSize = (int)(10f * zoom); - _counterStyle.normal.textColor = new Color(1f, 0f, 1f); - //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); + _counterStyle.fontSize = (int)(10f * zoom); + _counterStyle.normal.textColor = new Color(1f, 0f, 1f); + //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); #if DEBUG - if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { - continue; - } + if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { + continue; + } #endif - String labelStr = "Inst. " + i + ", Cit. " + citizenInstance.m_citizen + ",\nm: " + ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode.ToString() + ", tm: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].transportMode + ", ltm: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].lastTransportMode + ", ll: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].lastLocation; - if (citizenInstance.m_citizen != 0) { - Citizen citizen = Singleton.instance.m_citizens.m_buffer[citizenInstance.m_citizen]; - if (citizen.m_parkedVehicle != 0) { - labelStr += "\nparked: " + citizen.m_parkedVehicle + " dist: " + (Singleton.instance.m_parkedVehicles.m_buffer[citizen.m_parkedVehicle].m_position - pos).magnitude; - } - if (citizen.m_vehicle != 0) { - labelStr += "\nveh: " + citizen.m_vehicle + " dist: " + (Singleton.instance.m_vehicles.m_buffer[citizen.m_vehicle].GetLastFramePosition() - pos).magnitude; - } - } + String labelStr = "Inst. " + i + ", Cit. " + citizenInstance.m_citizen + ",\nm: " + ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode.ToString() + ", tm: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].transportMode + ", ltm: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].lastTransportMode + ", ll: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].lastLocation; + if (citizenInstance.m_citizen != 0) { + Citizen citizen = Singleton.instance.m_citizens.m_buffer[citizenInstance.m_citizen]; + if (citizen.m_parkedVehicle != 0) { + labelStr += "\nparked: " + citizen.m_parkedVehicle + " dist: " + (Singleton.instance.m_parkedVehicles.m_buffer[citizen.m_parkedVehicle].m_position - pos).magnitude; + } + if (citizen.m_vehicle != 0) { + labelStr += "\nveh: " + citizen.m_vehicle + " dist: " + (Singleton.instance.m_vehicles.m_buffer[citizen.m_vehicle].GetLastFramePosition() - pos).magnitude; + } + } + + Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); + Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); + + GUI.Box(labelRect, labelStr, _counterStyle); + } + } - Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); - Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); + private void _guiBuildings() { + GUIStyle _counterStyle = new GUIStyle(); + Array16 buildings = Singleton.instance.m_buildings; + for (int i = 1; i < buildings.m_size; ++i) { + Building building = buildings.m_buffer[i]; + if (building.m_flags == Building.Flags.None) + continue; - GUI.Box(labelRect, labelStr, _counterStyle); - } - } + Vector3 pos = building.m_position; + Vector3 screenPos; + bool visible = WorldToScreenPoint(pos, out screenPos); - private void _guiBuildings() { - GUIStyle _counterStyle = new GUIStyle(); - Array16 buildings = Singleton.instance.m_buildings; - for (int i = 1; i < buildings.m_size; ++i) { - Building building = buildings.m_buffer[i]; - if (building.m_flags == Building.Flags.None) - continue; + if (! visible) + continue; - Vector3 pos = building.m_position; - Vector3 screenPos; - bool visible = WorldToScreenPoint(pos, out screenPos); - - if (! visible) - continue; + var camPos = Singleton.instance.m_simulationView.m_position; + var diff = pos - camPos; + if (diff.magnitude > DebugCloseLod) + continue; // do not draw if too distant + + var zoom = 1.0f / diff.magnitude * 150f; + + _counterStyle.fontSize = (int)(10f * zoom); + _counterStyle.normal.textColor = new Color(0f, 1f, 0f); + //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); - var camPos = Singleton.instance.m_simulationView.m_position; - var diff = pos - camPos; - if (diff.magnitude > DebugCloseLod) - continue; // do not draw if too distant + ExtBuilding extBuilding = ExtBuildingManager.Instance.ExtBuildings[i]; - var zoom = 1.0f / diff.magnitude * 150f; + String labelStr = "Building " + i + ", PDemand: " + extBuilding.parkingSpaceDemand + ", IncTDem: " + extBuilding.incomingPublicTransportDemand + ", OutTDem: " + extBuilding.outgoingPublicTransportDemand; - _counterStyle.fontSize = (int)(10f * zoom); - _counterStyle.normal.textColor = new Color(0f, 1f, 0f); - //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); + Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); + Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); - ExtBuilding extBuilding = ExtBuildingManager.Instance.ExtBuildings[i]; + GUI.Box(labelRect, labelStr, _counterStyle); + } + } - String labelStr = "Building " + i + ", PDemand: " + extBuilding.parkingSpaceDemand + ", IncTDem: " + extBuilding.incomingPublicTransportDemand + ", OutTDem: " + extBuilding.outgoingPublicTransportDemand; + new internal Color GetToolColor(bool warning, bool error) { + return base.GetToolColor(warning, error); + } - Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); - Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); + internal static int GetSegmentNumVehicleLanes(ushort segmentId, ushort? nodeId, out int numDirections, VehicleInfo.VehicleType vehicleTypeFilter) { + NetManager netManager = Singleton.instance; - GUI.Box(labelRect, labelStr, _counterStyle); - } - } + var info = netManager.m_segments.m_buffer[segmentId].Info; + var curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + var laneIndex = 0; - new internal Color GetToolColor(bool warning, bool error) { - return base.GetToolColor(warning, error); - } + NetInfo.Direction? dir = null; + NetInfo.Direction? dir2 = null; + //NetInfo.Direction? dir3 = null; - internal static int GetSegmentNumVehicleLanes(ushort segmentId, ushort? nodeId, out int numDirections, VehicleInfo.VehicleType vehicleTypeFilter) { - NetManager netManager = Singleton.instance; + numDirections = 0; + HashSet directions = new HashSet(); - var info = netManager.m_segments.m_buffer[segmentId].Info; - var curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - var laneIndex = 0; + if (nodeId != null) { + dir = (netManager.m_segments.m_buffer[segmentId].m_startNode == nodeId) ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; + dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection((NetInfo.Direction)dir); + //dir3 = TrafficPriorityManager.IsLeftHandDrive() ? NetInfo.InvertDirection((NetInfo.Direction)dir2) : dir2; + } - NetInfo.Direction? dir = null; - NetInfo.Direction? dir2 = null; - //NetInfo.Direction? dir3 = null; + var numLanes = 0; - numDirections = 0; - HashSet directions = new HashSet(); + while (laneIndex < info.m_lanes.Length && curLaneId != 0u) { + if (((info.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && + (info.m_lanes[laneIndex].m_vehicleType & vehicleTypeFilter) != VehicleInfo.VehicleType.None) && + (dir2 == null || info.m_lanes[laneIndex].m_finalDirection == dir2)) { - if (nodeId != null) { - dir = (netManager.m_segments.m_buffer[segmentId].m_startNode == nodeId) ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; - dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection((NetInfo.Direction)dir); - //dir3 = TrafficPriorityManager.IsLeftHandDrive() ? NetInfo.InvertDirection((NetInfo.Direction)dir2) : dir2; - } + if (!directions.Contains(info.m_lanes[laneIndex].m_finalDirection)) { + directions.Add(info.m_lanes[laneIndex].m_finalDirection); + ++numDirections; + } + numLanes++; + } - var numLanes = 0; + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + laneIndex++; + } - while (laneIndex < info.m_lanes.Length && curLaneId != 0u) { - if (((info.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && - (info.m_lanes[laneIndex].m_vehicleType & vehicleTypeFilter) != VehicleInfo.VehicleType.None) && - (dir2 == null || info.m_lanes[laneIndex].m_finalDirection == dir2)) { + return numLanes; + } - if (!directions.Contains(info.m_lanes[laneIndex].m_finalDirection)) { - directions.Add(info.m_lanes[laneIndex].m_finalDirection); - ++numDirections; - } - numLanes++; - } - - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - laneIndex++; - } - - return numLanes; - } - - internal static void CalculateSegmentCenterByDir(ushort segmentId, Dictionary segmentCenterByDir) { - segmentCenterByDir.Clear(); - NetManager netManager = Singleton.instance; - - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - Dictionary numCentersByDir = new Dictionary(); - uint laneIndex = 0; - while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { - if ((segmentInfo.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) == NetInfo.LaneType.None) - goto nextIter; - - NetInfo.Direction dir = segmentInfo.m_lanes[laneIndex].m_finalDirection; - Vector3 bezierCenter = netManager.m_lanes.m_buffer[curLaneId].m_bezier.Position(0.5f); - - if (!segmentCenterByDir.ContainsKey(dir)) { - segmentCenterByDir[dir] = bezierCenter; - numCentersByDir[dir] = 1; - } else { - segmentCenterByDir[dir] += bezierCenter; - numCentersByDir[dir]++; - } - - nextIter: - - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - laneIndex++; - } - - foreach (KeyValuePair e in numCentersByDir) { - segmentCenterByDir[e.Key] /= (float)e.Value; - } - } - - public static Texture2D MakeTex(int width, int height, Color col) { - var pix = new Color[width * height]; - - for (var i = 0; i < pix.Length; i++) - pix[i] = col; - - var result = new Texture2D(width, height); - result.SetPixels(pix); - result.Apply(); - - return result; - } - - public static Texture2D AdjustAlpha(Texture2D tex, float alpha) { - Color[] texColors = tex.GetPixels(); - Color[] retPixels = new Color[texColors.Length]; - - for (int i = 0; i < texColors.Length; ++i) { - retPixels[i] = new Color(texColors[i].r, texColors[i].g, texColors[i].b, texColors[i].a * alpha); - } - - Texture2D ret = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false); - - ret.SetPixels(retPixels); - ret.Apply(); - - return ret; - } - - internal static bool IsMouseOver(Rect boundingBox) { - return boundingBox.Contains(Event.current.mousePosition); - } - - internal bool CheckClicked() { - if (Input.GetMouseButtonDown(0) && !mouseClickProcessed) { - mouseClickProcessed = true; - return true; - } - return false; - } - - public void ShowTooltip(String text) { - if (text == null) - return; - - UIView.library.ShowModal("ExceptionPanel").SetMessage("Info", text, false); - - /*tooltipStartFrame = currentFrame; - tooltipText = text; - tooltipWorldPos = position;*/ - } - } -} + internal static void CalculateSegmentCenterByDir(ushort segmentId, Dictionary segmentCenterByDir) { + segmentCenterByDir.Clear(); + NetManager netManager = Singleton.instance; + + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + Dictionary numCentersByDir = new Dictionary(); + uint laneIndex = 0; + while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { + if ((segmentInfo.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) == NetInfo.LaneType.None) + goto nextIter; + + NetInfo.Direction dir = segmentInfo.m_lanes[laneIndex].m_finalDirection; + Vector3 bezierCenter = netManager.m_lanes.m_buffer[curLaneId].m_bezier.Position(0.5f); + + if (!segmentCenterByDir.ContainsKey(dir)) { + segmentCenterByDir[dir] = bezierCenter; + numCentersByDir[dir] = 1; + } else { + segmentCenterByDir[dir] += bezierCenter; + numCentersByDir[dir]++; + } + + nextIter: + + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + laneIndex++; + } + + foreach (KeyValuePair e in numCentersByDir) { + segmentCenterByDir[e.Key] /= (float)e.Value; + } + } + + public static Texture2D MakeTex(int width, int height, Color col) { + var pix = new Color[width * height]; + + for (var i = 0; i < pix.Length; i++) + pix[i] = col; + + var result = new Texture2D(width, height); + result.SetPixels(pix); + result.Apply(); + + return result; + } + + public static Texture2D AdjustAlpha(Texture2D tex, float alpha) { + Color[] texColors = tex.GetPixels(); + Color[] retPixels = new Color[texColors.Length]; + + for (int i = 0; i < texColors.Length; ++i) { + retPixels[i] = new Color(texColors[i].r, texColors[i].g, texColors[i].b, texColors[i].a * alpha); + } + + Texture2D ret = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false); + + ret.SetPixels(retPixels); + ret.Apply(); + + return ret; + } + + internal static bool IsMouseOver(Rect boundingBox) { + return boundingBox.Contains(Event.current.mousePosition); + } + + internal bool CheckClicked() { + if (Input.GetMouseButtonDown(0) && !mouseClickProcessed) { + mouseClickProcessed = true; + return true; + } + return false; + } + + public void ShowTooltip(String text) { + if (text == null) + return; + + UIView.library.ShowModal("ExceptionPanel").SetMessage("Info", text, false); + + /*tooltipStartFrame = currentFrame; + tooltipText = text; + tooltipWorldPos = position;*/ + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/UIBase.cs b/TLM/TLM/UI/UIBase.cs index b8aee6b54..4f1d48bf6 100644 --- a/TLM/TLM/UI/UIBase.cs +++ b/TLM/TLM/UI/UIBase.cs @@ -1,184 +1,179 @@ using ColossalFramework.UI; using CSUtil.Commons; using System; -using System.Collections.Generic; -using TrafficManager.State; -using TrafficManager.TrafficLight; using TrafficManager.UI.MainMenu; -using TrafficManager.Util; -using UnityEngine; namespace TrafficManager.UI { - public class UIBase : UICustomControl { + public class UIBase : UICustomControl { - public UIMainMenuButton MainMenuButton { get; private set; } - public MainMenuPanel MainMenu { get; private set; } + public UIMainMenuButton MainMenuButton { get; private set; } + public MainMenuPanel MainMenu { get; private set; } #if DEBUG - public DebugMenuPanel DebugMenu { get; private set; } + public DebugMenuPanel DebugMenu { get; private set; } #endif - public static TrafficManagerTool GetTrafficManagerTool(bool createIfRequired=true) { - if (tool == null && createIfRequired) { - Log.Info("Initializing traffic manager tool..."); - tool = ToolsModifierControl.toolController.gameObject.GetComponent() ?? - ToolsModifierControl.toolController.gameObject.AddComponent(); - tool.Initialize(); - } - - return tool; - } - private static TrafficManagerTool tool = null; - public static TrafficManagerMode ToolMode { get; set; } - - private bool _uiShown = false; - - public UIBase() { - Log._Debug("##### Initializing UIBase."); - - // Get the UIView object. This seems to be the top-level object for most - // of the UI. - var uiView = UIView.GetAView(); - - // Add a new button to the view. - MainMenuButton = (UIMainMenuButton)uiView.AddUIComponent(typeof(UIMainMenuButton)); - - // add the menu - MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); - MainMenu.gameObject.AddComponent(); + public static TrafficManagerTool GetTrafficManagerTool(bool createIfRequired=true) { + if (tool == null && createIfRequired) { + Log.Info("Initializing traffic manager tool..."); + tool = ToolsModifierControl.toolController.gameObject.GetComponent() ?? + ToolsModifierControl.toolController.gameObject.AddComponent(); + tool.Initialize(); + } + + return tool; + } + private static TrafficManagerTool tool = null; + public static TrafficManagerMode ToolMode { get; set; } + + private bool _uiShown = false; + + public UIBase() { + Log._Debug("##### Initializing UIBase."); + + // Get the UIView object. This seems to be the top-level object for most + // of the UI. + var uiView = UIView.GetAView(); + + // Add a new button to the view. + MainMenuButton = (UIMainMenuButton)uiView.AddUIComponent(typeof(UIMainMenuButton)); + + // add the menu + MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); + MainMenu.gameObject.AddComponent(); #if DEBUG - DebugMenu = (DebugMenuPanel)uiView.AddUIComponent(typeof(DebugMenuPanel)); + DebugMenu = (DebugMenuPanel)uiView.AddUIComponent(typeof(DebugMenuPanel)); #endif - ToolMode = TrafficManagerMode.None; - } - - ~UIBase() { - UnityEngine.Object.Destroy(MainMenuButton); - UnityEngine.Object.Destroy(MainMenu); - } - - public bool IsVisible() { - return _uiShown; - } - - public void ToggleMainMenu() { - if (IsVisible()) - Close(); - else - Show(); - } - - internal void RebuildMenu() { - Close(); - if (MainMenu != null) { - CustomKeyHandler keyHandler = MainMenu.GetComponent(); - if(keyHandler != null) - UnityEngine.Object.Destroy(keyHandler); - - UnityEngine.Object.Destroy(MainMenu); + ToolMode = TrafficManagerMode.None; + } + + ~UIBase() { + UnityEngine.Object.Destroy(MainMenuButton); + UnityEngine.Object.Destroy(MainMenu); + } + + public bool IsVisible() { + return _uiShown; + } + + public void ToggleMainMenu() { + if (IsVisible()) + Close(); + else + Show(); + } + + internal void RebuildMenu() { + Close(); + if (MainMenu != null) { + CustomKeyHandler keyHandler = MainMenu.GetComponent(); + if(keyHandler != null) + UnityEngine.Object.Destroy(keyHandler); + + UnityEngine.Object.Destroy(MainMenu); #if DEBUG - UnityEngine.Object.Destroy(DebugMenu); + UnityEngine.Object.Destroy(DebugMenu); #endif - } - var uiView = UIView.GetAView(); - MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); - MainMenu.gameObject.AddComponent(); + } + var uiView = UIView.GetAView(); + MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); + MainMenu.gameObject.AddComponent(); #if DEBUG - DebugMenu = (DebugMenuPanel)uiView.AddUIComponent(typeof(DebugMenuPanel)); + DebugMenu = (DebugMenuPanel)uiView.AddUIComponent(typeof(DebugMenuPanel)); #endif - } - - public void Show() { - try { - ToolsModifierControl.mainToolbar.CloseEverything(); - } catch (Exception e) { - Log.Error("Error on Show(): " + e.ToString()); - } - - foreach (var button in GetMenu().Buttons) { - button.UpdateProperties(); - } - - GetMenu().Show(); - Translation.ReloadTutorialTranslations(); - TrafficManagerTool.ShowAdvisor("MainMenu"); + } + + public void Show() { + try { + ToolsModifierControl.mainToolbar.CloseEverything(); + } catch (Exception e) { + Log.Error("Error on Show(): " + e.ToString()); + } + + foreach (var button in GetMenu().Buttons) { + button.UpdateProperties(); + } + + GetMenu().Show(); + Translation.ReloadTutorialTranslations(); + TrafficManagerTool.ShowAdvisor("MainMenu"); #if DEBUG - GetDebugMenu().Show(); + GetDebugMenu().Show(); #endif - SetToolMode(TrafficManagerMode.Activated); - _uiShown = true; - MainMenuButton.UpdateSprites(); - UIView.SetFocus(MainMenu); - } - - public void Close() { - var uiView = UIView.GetAView(); - GetMenu().Hide(); + SetToolMode(TrafficManagerMode.Activated); + _uiShown = true; + MainMenuButton.UpdateSprites(); + UIView.SetFocus(MainMenu); + } + + public void Close() { + var uiView = UIView.GetAView(); + GetMenu().Hide(); #if DEBUG - GetDebugMenu().Hide(); + GetDebugMenu().Hide(); #endif - TrafficManagerTool tmTool = GetTrafficManagerTool(false); - if (tmTool != null) { - tmTool.SetToolMode(UI.ToolMode.None); - } - SetToolMode(TrafficManagerMode.None); - _uiShown = false; - MainMenuButton.UpdateSprites(); - } - - internal MainMenuPanel GetMenu() { - return MainMenu; - } + TrafficManagerTool tmTool = GetTrafficManagerTool(false); + if (tmTool != null) { + tmTool.SetToolMode(UI.ToolMode.None); + } + SetToolMode(TrafficManagerMode.None); + _uiShown = false; + MainMenuButton.UpdateSprites(); + } + + internal MainMenuPanel GetMenu() { + return MainMenu; + } #if DEBUG - internal DebugMenuPanel GetDebugMenu() { - return DebugMenu; - } + internal DebugMenuPanel GetDebugMenu() { + return DebugMenu; + } #endif - public static void SetToolMode(TrafficManagerMode mode) { - if (mode == ToolMode) return; - - ToolMode = mode; - - if (mode != TrafficManagerMode.None) { - EnableTool(); - } else { - DisableTool(); - } - } - - public static void EnableTool() { - Log._Debug("LoadingExtension.EnableTool: called"); - TrafficManagerTool tmTool = GetTrafficManagerTool(true); - - ToolsModifierControl.toolController.CurrentTool = tmTool; - ToolsModifierControl.SetTool(); - } - - public static void DisableTool() { - Log._Debug("LoadingExtension.DisableTool: called"); - ToolsModifierControl.toolController.CurrentTool = ToolsModifierControl.GetTool(); - ToolsModifierControl.SetTool(); - } - - internal static void ReleaseTool() { - if (ToolMode != TrafficManagerMode.None) { - ToolMode = TrafficManagerMode.None; - DestroyTool(); - } - } - - private static void DestroyTool() { - if (ToolsModifierControl.toolController != null) { - ToolsModifierControl.toolController.CurrentTool = ToolsModifierControl.GetTool(); - ToolsModifierControl.SetTool(); - - if (tool != null) { - UnityEngine.Object.Destroy(tool); - tool = null; - } - } else - Log.Warning("LoadingExtensions.DestroyTool: ToolsModifierControl.toolController is null!"); - } - } -} + public static void SetToolMode(TrafficManagerMode mode) { + if (mode == ToolMode) return; + + ToolMode = mode; + + if (mode != TrafficManagerMode.None) { + EnableTool(); + } else { + DisableTool(); + } + } + + public static void EnableTool() { + Log._Debug("LoadingExtension.EnableTool: called"); + TrafficManagerTool tmTool = GetTrafficManagerTool(true); + + ToolsModifierControl.toolController.CurrentTool = tmTool; + ToolsModifierControl.SetTool(); + } + + public static void DisableTool() { + Log._Debug("LoadingExtension.DisableTool: called"); + ToolsModifierControl.toolController.CurrentTool = ToolsModifierControl.GetTool(); + ToolsModifierControl.SetTool(); + } + + internal static void ReleaseTool() { + if (ToolMode != TrafficManagerMode.None) { + ToolMode = TrafficManagerMode.None; + DestroyTool(); + } + } + + private static void DestroyTool() { + if (ToolsModifierControl.toolController != null) { + ToolsModifierControl.toolController.CurrentTool = ToolsModifierControl.GetTool(); + ToolsModifierControl.SetTool(); + + if (tool != null) { + UnityEngine.Object.Destroy(tool); + tool = null; + } + } else + Log.Warning("LoadingExtensions.DestroyTool: ToolsModifierControl.toolController is null!"); + } + } +} \ No newline at end of file From 321b3fb3a9559fdbf223569a364446efa8e37028 Mon Sep 17 00:00:00 2001 From: krzychu124 Date: Sat, 29 Jun 2019 00:36:39 +0200 Subject: [PATCH 101/142] Explicitly set C# language version to 7.3 --- TLM/TLM/TLM.csproj | 1 + TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj | 1 + TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj | 1 + TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj | 1 + TLM/TMPE.UnitTest/TMPE.UnitTest.csproj | 1 + 5 files changed, 5 insertions(+) diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 404934bdd..c08ca2a3f 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -12,6 +12,7 @@ v3.5 512 + 7.3 true diff --git a/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj b/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj index e6ced5290..2d1705a34 100644 --- a/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj +++ b/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj @@ -12,6 +12,7 @@ v3.5 512 + 7.3 true diff --git a/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj b/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj index 3e498fb0b..fe169164f 100644 --- a/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj +++ b/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj @@ -12,6 +12,7 @@ v3.5 512 + 7.3 true diff --git a/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj b/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj index 68e466994..cb883f46a 100644 --- a/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj +++ b/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj @@ -12,6 +12,7 @@ v3.5 512 + 7.3 true diff --git a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj index 54dd5916f..aa57d72f9 100644 --- a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj +++ b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj @@ -17,6 +17,7 @@ False UnitTest + 7.3 true From cfd7395bba002ae999fbc5fa8ed48963fa5759e8 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sat, 29 Jun 2019 12:10:23 +0200 Subject: [PATCH 102/142] Translations adjusted; Main menu button has tooltip now --- TLM/TLM/Resources/lang.txt | 3 +- TLM/TLM/Resources/lang_de.txt | 1 + TLM/TLM/Resources/lang_es.txt | 1 + TLM/TLM/Resources/lang_fr.txt | 1 + TLM/TLM/Resources/lang_it.txt | 1 + TLM/TLM/Resources/lang_ja.txt | 11 +- TLM/TLM/Resources/lang_ko.txt | 1 + TLM/TLM/Resources/lang_nl.txt | 1 + TLM/TLM/Resources/lang_pl.txt | 3 +- TLM/TLM/Resources/lang_pt.txt | 1 + TLM/TLM/Resources/lang_ru.txt | 1 + TLM/TLM/Resources/lang_template.txt | 14 +- TLM/TLM/Resources/lang_zh-tw.txt | 7 +- TLM/TLM/Resources/lang_zh.txt | 1 + TLM/TLM/State/Keybinds/Keybind.cs | 24 ++ TLM/TLM/State/Keybinds/KeymappingSettings.cs | 29 +- TLM/TLM/State/Options.cs | 359 ++++++++----------- TLM/TLM/TLM.csproj | 1 + TLM/TLM/TrafficManagerMod.cs | 61 ++-- TLM/TLM/UI/LinearSpriteButton.cs | 269 +++++++------- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 4 +- TLM/TLM/UI/MainMenu/MenuButton.cs | 144 +++----- TLM/TLM/UI/MainMenu/VersionLabel.cs | 24 +- TLM/TLM/UI/UIBase.cs | 15 +- TLM/TLM/UI/UIMainMenuButton.cs | 281 ++++++++------- 25 files changed, 626 insertions(+), 632 deletions(-) create mode 100644 TLM/TLM/State/Keybinds/Keybind.cs diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index d6edd4ba9..0e147cd4a 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -239,7 +239,8 @@ Keybind_use_lane_connections_tool Use 'Lane Connections' Tool Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool -Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_stay_in_lane Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index b12c2c8a2..f97ec77bc 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Werkzeug 'Vorfahrtsschilder' verwenden Keybind_use_junction_restrictions_tool Werkzeug 'Kreuzungsbeschränkungen' verwenden Keybind_use_speed_limits_tool Werkzeug 'Geschwindigkeitsbeschränkungen' verwenden Keybind_lane_connector_stay_in_lane Fahrspurverbinder: Bleib auf der Fahrspur +Keybind_lane_connector_delete Fahrspurverbindungen löschen Keybinds Tastenkombinationen Keybind_Exit_subtool Werkzeug und TM:PE Menü schließen Keybind_conflict Die ausgewählte Taste steht in Konflikt mit einer anderen Tastenkombination. Bitte wähle etwas anderes. diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index ffb36dd2e..50a99c32c 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index 922c9c858..179cf723d 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index 86b5f96cc..eb1c27d84 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index 7f1e62c3e..c1e738948 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -85,9 +85,9 @@ Add_zoning 区間の追加 Remove_zoning 区間の削除 Lane_Arrow_Changer_Disabled_Highway 高速道路ルールシステムを適用しているため、この車線の矢印は変更できません Add_junction_to_timed_light この信号に交差点を追加 -Remove_junction_from_timed_light この信号から交差点を削除 -Select_junction 交差点を選択してください -Cancel キャンセル +Remove_junction_from_timed_light この信号から交差点を削除 +Select_junction 交差点を選択してください +Cancel キャンセル Speed_limits 速度制限 Persistently_visible_overlays オーバーレイ表示し続ける情報 Priority_signs 優先関係標識 @@ -209,10 +209,10 @@ TMPE_TUTORIAL_BODY_VehicleRestrictionsTool 特定の道路区間の車両を禁 TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults デフォルトの制限速度 TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. 上半分の矢印を使用して、すべての道路タイプを順に切り替えます。\n2. 下半分で、制限速度を選択します。\n3. 「保存」をクリックして、選択した制限速度を選択したタイプの将来の道路区間のデフォルトとして設定します。 選択した種類の既存の道路をすべて更新するには、「保存して適用」をクリックします。 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep 時間設定を追加する -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. ゲーム内オーバーレイ内で、信号機をクリックして状態を変更します。 矢印信号を追加するには、「モードの変更」ボタンを使用します。\n2. ステップの最短時間と最長時間の両方を入力します。 最短時間が経過すると、信号機が接近してくる車両を数えて比較します。\n3. 必要に応じて、ステップ切り替えタイプを選択します。 デフォルトでは、待っているよりもおおよそ少ない車両が運転している場合、ステップは変わります。\n4. 必要に応じて、感度を調整してください。 たとえば、スライダーを左に動かすと、待機中の車両に対するタイムシグナルの感度が低くなります。 +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. ゲーム内オーバーレイ内で、信号機をクリックして状態を変更します。 矢印信号を追加するには、「モードの変更」ボタンを使用します。\n2. ステップの最短時間と最長時間の両方を入力します。 最短時間が経過すると、信号機が接近してくる車両を数えて比較します。\n3. 必要に応じて、ステップ切り替えタイプを選択します。 デフォルトでは、待っているよりもおおよそ少ない車両が運転している場合、ステップは変わります。\n4. 必要に応じて、感度を調整してください。 たとえば、スライダーを左に動かすと、待機中の車両に対するタイムシグナルの感度が低くなります。 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy 時間設定付き信号をコピーする TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy 別の交差点をクリックして時間設定付き信号を貼り付けます。\n\n操作をキャンセルするには、マウスの右ボタンで任意の場所をクリックします。 -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction 時間設定付き信号機に交差点を追加する +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction 時間設定付き信号機に交差点を追加する TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction 追加するには、他の交差点をクリックします。 両方の信号は、時間設定プログラムが両方の交差点を一度に制御するように結合されます。\n\n操作をキャンセルするには、マウスの右ボタンで任意の場所をクリックします。 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction 時間設定付き信号機から交差点を削除する TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction この時間設定プログラムによって制御されている交差点の1つをクリックします。 選択した信号機は削除され、時間設定プログラムによって管理されなくなります。\n\n操作をキャンセルするには、マウスの右ボタンで任意の場所をクリックします。 @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index 4216944fa..0141f361a 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 3ed8df31b..ff388156b 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 567a9fcb9..6178ef880 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Narzędzie 'Znaki pierwszeństwa' Keybind_use_junction_restrictions_tool Narzędzie 'Ograniczenia na skrzyżowaniu' Keybind_use_speed_limits_tool Narzędzie 'Limity prędkości' Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie +Keybind_lane_connector_delete Oczyść połączenia pasów ruchu Keybinds Skróty klawiszowe Keybind_Exit_subtool Wyłącz narzędzie i zamknij TM:PE menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. @@ -248,7 +249,7 @@ Keybind_category_LaneConnector Lane Connector Tool keys Display_speed_limits_mph Wyświetlaj limity prędkości w mph zamiast km/h Miles_per_hour Mile na godzinę Kilometers_per_hour Kilometry na godzinę -Road_signs_theme_mph Motyw dla znaków MPH +Road_signs_theme_mph Motyw dla znaków MPH theme_Square_US Stany zjednoczone theme_Round_UK Wielka Brytania theme_Round_German Niemcy diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index da9a80148..437ea8629 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index b8cd4dacf..6ba518b4e 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Инструмент "Знаки приорит Keybind_use_junction_restrictions_tool Инструмент "Ограничения на перекрёстках" Keybind_use_speed_limits_tool Инструмент "Ограничения скорости" Keybind_lane_connector_stay_in_lane Линии движения: Оставаться в своей полосе +Keybind_lane_connector_delete Очистить соединения линий движения Keybinds Горячие клавиши Keybind_Exit_subtool Закрыть инструмент и меню TM:PE Keybind_conflict Выбранная комбинация клавиш уже используется в другом месте. Пожалуйста, выберите другую комбинацию. diff --git a/TLM/TLM/Resources/lang_template.txt b/TLM/TLM/Resources/lang_template.txt index 255d5ca5a..02004701a 100644 --- a/TLM/TLM/Resources/lang_template.txt +++ b/TLM/TLM/Resources/lang_template.txt @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool TMPE_TUTORIAL_BODY_VehicleRestrictionsTool TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction @@ -240,8 +240,16 @@ Keybind_use_priority_signs_tool Keybind_use_junction_restrictions_tool Keybind_use_speed_limits_tool Keybind_lane_connector_stay_in_lane +Keybind_lane_connector_delete Keybinds Keybind_Exit_subtool Keybind_conflict Keybind_category_Global Keybind_category_LaneConnector +Display_speed_limits_mph +Miles_per_hour +Kilometers_per_hour +Road_signs_theme_mph +theme_Square_US +theme_Round_UK +theme_Round_German diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index b20c6a3b0..337aad505 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -208,11 +208,11 @@ TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step -TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index 814779300..3b080c336 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -240,6 +240,7 @@ Keybind_use_priority_signs_tool Use 'Priority Signs' Tool Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool Keybind_use_speed_limits_tool Use 'Speed Limits' Tool Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections Keybinds Keybinds Keybind_Exit_subtool Exit tool and close TM:PE Menu Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. diff --git a/TLM/TLM/State/Keybinds/Keybind.cs b/TLM/TLM/State/Keybinds/Keybind.cs new file mode 100644 index 000000000..3c5c16d6c --- /dev/null +++ b/TLM/TLM/State/Keybinds/Keybind.cs @@ -0,0 +1,24 @@ +using ColossalFramework; +using UnityEngine; + +namespace TrafficManager.State.Keybinds { + /// + /// General input key handling functions, checking for empty, converting to string etc. + /// + public class Keybind { + public static bool IsEmpty(InputKey sample) { + var noKey = SavedInputKey.Encode(KeyCode.None, false, false, false); + return sample == SavedInputKey.Empty + || sample == noKey; + } + + /// + /// Returns shortcut as a string in user's language. Modify for special handling. + /// + /// The key + /// The shortcut, example: "Ctrl + Alt + H" + public static string Str(SavedInputKey k) { + return k.ToLocalizedString("KEYNAME"); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeymappingSettings.cs b/TLM/TLM/State/Keybinds/KeymappingSettings.cs index 42e1f4d0a..75846b12d 100644 --- a/TLM/TLM/State/Keybinds/KeymappingSettings.cs +++ b/TLM/TLM/State/Keybinds/KeymappingSettings.cs @@ -127,7 +127,7 @@ protected void AddKeymapping(string label, SavedInputKey savedInputKey, string c var uiButton = uiPanel.Find("Binding"); uiButton.eventKeyDown += OnBindingKeyDown; uiButton.eventMouseDown += OnBindingMouseDown; - uiButton.text = savedInputKey.ToLocalizedString("KEYNAME"); + uiButton.text = Keybind.Str(savedInputKey); uiButton.objectUserData = savedInputKey; uiButton.stringUserData = category; @@ -157,7 +157,7 @@ protected void AddReadOnlyKeymapping(string label, SavedInputKey inputKey) { // Set label text (as provided) and set button text from the InputKey uiLabel.text = label; - uiReadOnlyKey.text = inputKey.ToLocalizedString("KEYNAME"); + uiReadOnlyKey.text = Keybind.Str(inputKey); uiReadOnlyKey.objectUserData = inputKey; } @@ -227,7 +227,7 @@ protected KeyCode ButtonToKeycode(UIMouseButton button) { return KeyCode.None; } - protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { + private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { // This will only work if the user clicked the modify button // otherwise no effect if (editingBinding_ != null && !IsModifierKey(p.keycode)) { @@ -253,13 +253,13 @@ protected void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { } var uITextComponent = p.source as UITextComponent; - uITextComponent.text = editingBinding_.ToLocalizedString("KEYNAME"); + uITextComponent.text = Keybind.Str(editingBinding_); editingBinding_ = null; editingBindingCategory_ = string.Empty; } } - protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { + private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { // This will only work if the user is not in the process of changing the shortcut if (editingBinding_ == null) { p.Use(); @@ -292,20 +292,20 @@ protected void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { } var uIButton2 = p.source as UIButton; - uIButton2.text = editingBinding_.ToLocalizedString("KEYNAME"); + uIButton2.text = Keybind.Str(editingBinding_); uIButton2.buttonsMask = UIMouseButton.Left; editingBinding_ = null; editingBindingCategory_ = string.Empty; } } - protected void RefreshBindableInputs() { + private void RefreshBindableInputs() { foreach (var current in component.GetComponentsInChildren()) { var uITextComponent = current.Find("Binding"); if (uITextComponent != null) { var savedInputKey = uITextComponent.objectUserData as SavedInputKey; if (savedInputKey != null) { - uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); + uITextComponent.text = Keybind.Str(savedInputKey); } } @@ -316,16 +316,6 @@ protected void RefreshBindableInputs() { } } - protected void RefreshKeyMapping() { - foreach (var current in component.GetComponentsInChildren()) { - var uITextComponent = current.Find("Binding"); - var savedInputKey = (SavedInputKey) uITextComponent.objectUserData; - if (editingBinding_ != savedInputKey) { - uITextComponent.text = savedInputKey.ToLocalizedString("KEYNAME"); - } - } - } - /// /// For an inputkey, try find where possibly it is already used. /// This covers game Settings class, and self (OptionsKeymapping class). @@ -333,8 +323,7 @@ protected void RefreshKeyMapping() { /// Key to search for the conflicts /// private string FindConflict(InputKey sample, string sampleCategory) { - if (sample == SavedInputKey.Empty - || sample == SavedInputKey.Encode(KeyCode.None, false, false, false)) { + if (Keybind.IsEmpty(sample)) { // empty key never conflicts return string.Empty; } diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index fca58d05c..82ba59e1e 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -111,7 +111,7 @@ public class Options : MonoBehaviour { #if DEBUG true; #else - false; + false; #endif #endif @@ -121,10 +121,10 @@ public class Options : MonoBehaviour { public static bool citizenOverlay = false; public static bool buildingOverlay = false; #else - public static bool nodesOverlay = false; - public static bool vehicleOverlay = false; - public static bool citizenOverlay = false; - public static bool buildingOverlay = false; + public static bool nodesOverlay = false; + public static bool vehicleOverlay = false; + public static bool citizenOverlay = false; + public static bool buildingOverlay = false; #endif public static bool allowEnterBlockedJunctions = false; public static bool allowUTurns = false; @@ -140,7 +140,7 @@ public class Options : MonoBehaviour { #if DEBUG public static bool showLanes = true; #else - public static bool showLanes = false; + public static bool showLanes = false; #endif public static bool strongerRoadConditionEffects = false; public static bool prohibitPocketCars = false; @@ -172,7 +172,7 @@ internal set { } } - public static void makeSettings(UIHelperBase helper) { + public static void MakeSettings(UIHelperBase helper) { // tabbing code is borrowed from RushHour mod // https://github.com/PropaneDragon/RushHour/blob/release/RushHour/Options/OptionHandler.cs @@ -189,141 +189,38 @@ public static void makeSettings(UIHelperBase helper) { tabStrip.tabPages = tabContainer; int tabIndex = 0; - // GENERAL - - AddOptionTab(tabStrip, Translation.GetString("General"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); - tabStrip.selectedIndex = tabIndex; - - UIPanel currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; - - UIHelper panelHelper = new UIHelper(currentPanel); - - var generalGroup = panelHelper.AddGroup(Translation.GetString("General")); - - string[] languageLabels = new string[Translation.AVAILABLE_LANGUAGE_CODES.Count + 1]; - languageLabels[0] = Translation.GetString("Game_language"); - for (int i = 0; i < Translation.AVAILABLE_LANGUAGE_CODES.Count; ++i) { - languageLabels[i + 1] = Translation.LANGUAGE_LABELS[Translation.AVAILABLE_LANGUAGE_CODES[i]]; - } - int languageIndex = 0; - string curLangCode = GlobalConfig.Instance.LanguageCode; - if (curLangCode != null) { - languageIndex = Translation.AVAILABLE_LANGUAGE_CODES.IndexOf(curLangCode); - if (languageIndex < 0) { - languageIndex = 0; - } else { - ++languageIndex; - } - } - - languageDropdown = generalGroup.AddDropdown(Translation.GetString("Language") + ":", languageLabels, languageIndex, onLanguageChanged) as UIDropDown; - lockButtonToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_button_position"), GlobalConfig.Instance.Main.MainMenuButtonPosLocked, onLockButtonChanged) as UICheckBox; - lockMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_position"), GlobalConfig.Instance.Main.MainMenuPosLocked, onLockMenuChanged) as UICheckBox; - tinyMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Compact_main_menu"), GlobalConfig.Instance.Main.TinyMainMenu, onTinyMenuChanged) as UICheckBox; - guiTransparencySlider = generalGroup.AddSlider(Translation.GetString("Window_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.GuiTransparency, onGuiTransparencyChanged) as UISlider; - guiTransparencySlider.parent.Find("Label").width = 500; - overlayTransparencySlider = generalGroup.AddSlider(Translation.GetString("Overlay_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.OverlayTransparency, onOverlayTransparencyChanged) as UISlider; - overlayTransparencySlider.parent.Find("Label").width = 500; - enableTutorialToggle = generalGroup.AddCheckbox(Translation.GetString("Enable_tutorial_messages"), GlobalConfig.Instance.Main.EnableTutorial, onEnableTutorialsChanged) as UICheckBox; - showCompatibilityCheckErrorToggle = generalGroup.AddCheckbox(Translation.GetString("Notify_me_if_there_is_an_unexpected_mod_conflict"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; - scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; - ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; - Indent(ignoreDisabledModsToggle); - - // General: Speed Limits - setupSpeedLimitsPanel(panelHelper, generalGroup); - // General: Simulation - var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); - simAccuracyDropdown = simGroup.AddDropdown(Translation.GetString("Simulation_accuracy") + ":", new string[] { Translation.GetString("Very_high"), Translation.GetString("High"), Translation.GetString("Medium"), Translation.GetString("Low"), Translation.GetString("Very_Low") }, simAccuracy, onSimAccuracyChanged) as UIDropDown; - instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; + // GENERAL + UIPanel currentPanel; + UIHelper panelHelper; + MakeSettings_General(tabStrip, tabIndex); // GAMEPLAY ++tabIndex; - - AddOptionTab(tabStrip, Translation.GetString("Gameplay")); - tabStrip.selectedIndex = tabIndex; - - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; - - panelHelper = new UIHelper(currentPanel); - - var vehBehaviorGroup = panelHelper.AddGroup(Translation.GetString("Vehicle_behavior")); - - recklessDriversDropdown = vehBehaviorGroup.AddDropdown(Translation.GetString("Reckless_driving") + ":", new string[] { Translation.GetString("Path_Of_Evil_(10_%)"), Translation.GetString("Rush_Hour_(5_%)"), Translation.GetString("Minor_Complaints_(2_%)"), Translation.GetString("Holy_City_(0_%)") }, recklessDrivers, onRecklessDriversChanged) as UIDropDown; - recklessDriversDropdown.width = 300; - individualDrivingStyleToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Individual_driving_styles"), individualDrivingStyle, onIndividualDrivingStyleChanged) as UICheckBox; - - if (SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { - strongerRoadConditionEffectsToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Road_condition_has_a_bigger_impact_on_vehicle_speed"), strongerRoadConditionEffects, onStrongerRoadConditionEffectsChanged) as UICheckBox; - } - disableDespawningToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Disable_despawning"), disableDespawning, onDisableDespawningChanged) as UICheckBox; - - var vehAiGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI")); - advancedAIToggle = vehAiGroup.AddCheckbox(Translation.GetString("Enable_Advanced_Vehicle_AI"), advancedAI, onAdvancedAIChanged) as UICheckBox; - altLaneSelectionRatioSlider = vehAiGroup.AddSlider(Translation.GetString("Dynamic_lane_section") + ":", 0, 100, 5, altLaneSelectionRatio, onAltLaneSelectionRatioChanged) as UISlider; - altLaneSelectionRatioSlider.parent.Find("Label").width = 450; - - var parkAiGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI")); - prohibitPocketCarsToggle = parkAiGroup.AddCheckbox(Translation.GetString("Enable_more_realistic_parking"), prohibitPocketCars, onProhibitPocketCarsChanged) as UICheckBox; - - var ptGroup = panelHelper.AddGroup(Translation.GetString("Public_transport")); - realisticPublicTransportToggle = ptGroup.AddCheckbox(Translation.GetString("Prevent_excessive_transfers_at_public_transport_stations"), realisticPublicTransport, onRealisticPublicTransportChanged) as UICheckBox; + MakeSettings_Gameplay(tabStrip, tabIndex); // VEHICLE RESTRICTIONS ++tabIndex; + MakeSettings_VehicleRestrictions(tabStrip, tabIndex); - AddOptionTab(tabStrip, Translation.GetString("Policies_&_Restrictions")); - tabStrip.selectedIndex = tabIndex; - - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; - currentPanel.autoLayout = true; - currentPanel.autoLayoutDirection = LayoutDirection.Vertical; - currentPanel.autoLayoutPadding.top = 5; - currentPanel.autoLayoutPadding.left = 10; - currentPanel.autoLayoutPadding.right = 10; - - panelHelper = new UIHelper(currentPanel); - - var atJunctionsGroup = panelHelper.AddGroup(Translation.GetString("At_junctions")); -#if DEBUG - allRelaxedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("All_vehicles_may_ignore_lane_arrows"), allRelaxed, onAllRelaxedChanged) as UICheckBox; -#endif - relaxedBussesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Busses_may_ignore_lane_arrows"), relaxedBusses, onRelaxedBussesChanged) as UICheckBox; - allowEnterBlockedJunctionsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_enter_blocked_junctions"), allowEnterBlockedJunctions, onAllowEnterBlockedJunctionsChanged) as UICheckBox; - allowUTurnsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_do_u-turns_at_junctions"), allowUTurns, onAllowUTurnsChanged) as UICheckBox; - allowNearTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_turn_on_red"), allowNearTurnOnRed, onAllowNearTurnOnRedChanged) as UICheckBox; - allowFarTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Also_apply_to_left/right_turns_between_one-way_streets"), allowFarTurnOnRed, onAllowFarTurnOnRedChanged) as UICheckBox; - allowLaneChangesWhileGoingStraightToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_going_straight_may_change_lanes_at_junctions"), allowLaneChangesWhileGoingStraight, onAllowLaneChangesWhileGoingStraightChanged) as UICheckBox; - trafficLightPriorityRulesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights"), trafficLightPriorityRules, onTrafficLightPriorityRulesChanged) as UICheckBox; - - Indent(allowFarTurnOnRedToggle); - - var onRoadsGroup = panelHelper.AddGroup(Translation.GetString("On_roads")); - vehicleRestrictionsAggressionDropdown = onRoadsGroup.AddDropdown(Translation.GetString("Vehicle_restrictions_aggression") + ":", new string[] { Translation.GetString("Low"), Translation.GetString("Medium"), Translation.GetString("High"), Translation.GetString("Strict") }, (int)vehicleRestrictionsAggression, onVehicleRestrictionsAggressionChanged) as UIDropDown; - banRegularTrafficOnBusLanesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Ban_private_cars_and_trucks_on_bus_lanes"), banRegularTrafficOnBusLanes, onBanRegularTrafficOnBusLanesChanged) as UICheckBox; - highwayRulesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Enable_highway_specific_lane_merging/splitting_rules"), highwayRules, onHighwayRulesChanged) as UICheckBox; - preferOuterLaneToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Heavy_trucks_prefer_outer_lanes_on_highways"), preferOuterLane, onPreferOuterLaneChanged) as UICheckBox; + // OVERLAYS + ++tabIndex; + MakeSettings_Overlays(tabStrip, tabIndex); - if (SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) { - var inCaseOfEmergencyGroup = panelHelper.AddGroup(Translation.GetString("In_case_of_emergency")); - evacBussesMayIgnoreRulesToggle = inCaseOfEmergencyGroup.AddCheckbox(Translation.GetString("Evacuation_busses_may_ignore_traffic_rules"), evacBussesMayIgnoreRules, onEvacBussesMayIgnoreRulesChanged) as UICheckBox; - } + // MAINTENANCE + ++tabIndex; + MakeSettings_Maintenance(tabStrip, tabIndex); - // OVERLAYS + // KEYBOARD ++tabIndex; + MakeSettings_Keybinds(tabStrip, tabIndex); + tabStrip.selectedIndex = 0; + } - AddOptionTab(tabStrip, Translation.GetString("Overlays")); + private static void MakeSettings_Keybinds(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + AddOptionTab(tabStrip, Translation.GetString("Keybinds")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; @@ -335,24 +232,13 @@ public static void makeSettings(UIHelperBase helper) { panelHelper = new UIHelper(currentPanel); - prioritySignsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsOverlay, onPrioritySignsOverlayChanged) as UICheckBox; - timedLightsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsOverlay, onTimedLightsOverlayChanged) as UICheckBox; - speedLimitsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Speed_limits"), speedLimitsOverlay, onSpeedLimitsOverlayChanged) as UICheckBox; - vehicleRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsOverlay, onVehicleRestrictionsOverlayChanged) as UICheckBox; - parkingRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsOverlay, onParkingRestrictionsOverlayChanged) as UICheckBox; - junctionRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsOverlay, onJunctionRestrictionsOverlayChanged) as UICheckBox; - connectedLanesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Connected_lanes"), connectedLanesOverlay, onConnectedLanesOverlayChanged) as UICheckBox; - nodesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Nodes_and_segments"), nodesOverlay, onNodesOverlayChanged) as UICheckBox; - showLanesToggle = panelHelper.AddCheckbox(Translation.GetString("Lanes"), showLanes, onShowLanesChanged) as UICheckBox; -#if DEBUG - vehicleOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicles"), vehicleOverlay, onVehicleOverlayChanged) as UICheckBox; - citizenOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Citizens"), citizenOverlay, onCitizenOverlayChanged) as UICheckBox; - buildingOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Buildings"), buildingOverlay, onBuildingOverlayChanged) as UICheckBox; -#endif - - // MAINTENANCE - ++tabIndex; + var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); + ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); + } + private static void MakeSettings_Maintenance(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; AddOptionTab(tabStrip, Translation.GetString("Maintenance")); tabStrip.selectedIndex = tabIndex; @@ -390,11 +276,12 @@ public static void makeSettings(UIHelperBase helper) { enableLaneConnectorToggle = featureGroup.AddCheckbox(Translation.GetString("Lane_connector"), laneConnectorEnabled, onLaneConnectorEnabledChanged) as UICheckBox; Indent(turnOnRedEnabledToggle); + } - // KEYBOARD - ++tabIndex; - - AddOptionTab(tabStrip, Translation.GetString("Keybinds")); + private static void MakeSettings_Overlays(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + AddOptionTab(tabStrip, Translation.GetString("Overlays")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; @@ -406,13 +293,26 @@ public static void makeSettings(UIHelperBase helper) { panelHelper = new UIHelper(currentPanel); - var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); - ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); + prioritySignsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsOverlay, onPrioritySignsOverlayChanged) as UICheckBox; + timedLightsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsOverlay, onTimedLightsOverlayChanged) as UICheckBox; + speedLimitsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Speed_limits"), speedLimitsOverlay, onSpeedLimitsOverlayChanged) as UICheckBox; + vehicleRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsOverlay, onVehicleRestrictionsOverlayChanged) as UICheckBox; + parkingRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsOverlay, onParkingRestrictionsOverlayChanged) as UICheckBox; + junctionRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsOverlay, onJunctionRestrictionsOverlayChanged) as UICheckBox; + connectedLanesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Connected_lanes"), connectedLanesOverlay, onConnectedLanesOverlayChanged) as UICheckBox; + nodesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Nodes_and_segments"), nodesOverlay, onNodesOverlayChanged) as UICheckBox; + showLanesToggle = panelHelper.AddCheckbox(Translation.GetString("Lanes"), showLanes, onShowLanesChanged) as UICheckBox; #if DEBUG + vehicleOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicles"), vehicleOverlay, onVehicleOverlayChanged) as UICheckBox; + citizenOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Citizens"), citizenOverlay, onCitizenOverlayChanged) as UICheckBox; + buildingOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Buildings"), buildingOverlay, onBuildingOverlayChanged) as UICheckBox; +#endif + } - // GLOBAL CONFIG - /* - AddOptionTab(tabStrip, Translation.GetString("Global_configuration"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); + private static void MakeSettings_VehicleRestrictions(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + AddOptionTab(tabStrip, Translation.GetString("Policies_&_Restrictions")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; @@ -424,85 +324,122 @@ public static void makeSettings(UIHelperBase helper) { panelHelper = new UIHelper(currentPanel); - GlobalConfig globalConf = GlobalConfig.Instance; + var atJunctionsGroup = panelHelper.AddGroup(Translation.GetString("At_junctions")); +#if DEBUG + allRelaxedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("All_vehicles_may_ignore_lane_arrows"), allRelaxed, onAllRelaxedChanged) as UICheckBox; +#endif + relaxedBussesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Busses_may_ignore_lane_arrows"), relaxedBusses, onRelaxedBussesChanged) as UICheckBox; + allowEnterBlockedJunctionsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_enter_blocked_junctions"), allowEnterBlockedJunctions, onAllowEnterBlockedJunctionsChanged) as UICheckBox; + allowUTurnsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_do_u-turns_at_junctions"), allowUTurns, onAllowUTurnsChanged) as UICheckBox; + allowNearTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_turn_on_red"), allowNearTurnOnRed, onAllowNearTurnOnRedChanged) as UICheckBox; + allowFarTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Also_apply_to_left/right_turns_between_one-way_streets"), allowFarTurnOnRed, onAllowFarTurnOnRedChanged) as UICheckBox; + allowLaneChangesWhileGoingStraightToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_going_straight_may_change_lanes_at_junctions"), allowLaneChangesWhileGoingStraight, onAllowLaneChangesWhileGoingStraightChanged) as UICheckBox; + trafficLightPriorityRulesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights"), trafficLightPriorityRules, onTrafficLightPriorityRulesChanged) as UICheckBox; - var aiTrafficMeasurementConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("General")); + Indent(allowFarTurnOnRedToggle); - aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Live_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxTrafficBuffer, onMaxTrafficBufferChanged); - aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Path-find_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxPathFindTrafficBuffer, onMaxPathFindTrafficBufferChanged); - aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Max._congestion_measurements"), 0f, 1000f, 10f, globalConf.MaxNumCongestionMeasurements, onMaxNumCongestionMeasurementsChanged); + var onRoadsGroup = panelHelper.AddGroup(Translation.GetString("On_roads")); + vehicleRestrictionsAggressionDropdown = onRoadsGroup.AddDropdown(Translation.GetString("Vehicle_restrictions_aggression") + ":", new string[] { Translation.GetString("Low"), Translation.GetString("Medium"), Translation.GetString("High"), Translation.GetString("Strict") }, (int)vehicleRestrictionsAggression, onVehicleRestrictionsAggressionChanged) as UIDropDown; + banRegularTrafficOnBusLanesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Ban_private_cars_and_trucks_on_bus_lanes"), banRegularTrafficOnBusLanes, onBanRegularTrafficOnBusLanesChanged) as UICheckBox; + highwayRulesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Enable_highway_specific_lane_merging/splitting_rules"), highwayRules, onHighwayRulesChanged) as UICheckBox; + preferOuterLaneToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Heavy_trucks_prefer_outer_lanes_on_highways"), preferOuterLane, onPreferOuterLaneChanged) as UICheckBox; - var aiLaneSelParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_parameters")); + if (SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) { + var inCaseOfEmergencyGroup = panelHelper.AddGroup(Translation.GetString("In_case_of_emergency")); + evacBussesMayIgnoreRulesToggle = inCaseOfEmergencyGroup.AddCheckbox(Translation.GetString("Evacuation_busses_may_ignore_traffic_rules"), evacBussesMayIgnoreRules, onEvacBussesMayIgnoreRulesChanged) as UICheckBox; + } + } - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_randomization"), 0f, 100f, 1f, globalConf.LaneDensityRandInterval, onLaneDensityRandIntervalChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_discretization"), 0f, 100f, 1f, globalConf.LaneDensityDiscretization, onLaneTrafficDiscretizationChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_randomization"), 0f, 100f, 1f, globalConf.LaneUsageRandInterval, onLaneUsageRandIntervalChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_discretization"), 0f, 100f, 1f, globalConf.LaneUsageDiscretization, onLaneUsageDiscretizationChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Congestion_rel._velocity_threshold"), 0f, 100f, 1f, globalConf.CongestionSqrSpeedThreshold, onCongestionSqrSpeedThresholdChanged); - aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Max._walking_distance"), 0f, 10000f, 100f, globalConf.MaxWalkingDistance, onMaxWalkingDistanceChanged); + private static void MakeSettings_Gameplay(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + AddOptionTab(tabStrip, Translation.GetString("Gameplay")); + tabStrip.selectedIndex = tabIndex; - var aiLaneSelFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_factors")); + currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + currentPanel.autoLayout = true; + currentPanel.autoLayoutDirection = LayoutDirection.Vertical; + currentPanel.autoLayoutPadding.top = 5; + currentPanel.autoLayoutPadding.left = 10; + currentPanel.autoLayoutPadding.right = 10; - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Spread_randomization_factor"), 1f, 5f, 0.05f, globalConf.UsageCostFactor, onUsageCostFactorChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Traffic_avoidance_factor"), 1f, 5f, 0.05f, globalConf.TrafficCostFactor, onTrafficCostFactorChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_penalty"), 1f, 50f, 0.5f, globalConf.PublicTransportLanePenalty, onPublicTransportLanePenaltyChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_reward"), 0f, 1f, 0.05f, globalConf.PublicTransportLaneReward, onPublicTransportLaneRewardChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Heavy_vehicle_max._inner_lane_penalty"), 0f, 5f, 0.05f, globalConf.HeavyVehicleMaxInnerLanePenalty, onHeavyVehicleMaxInnerLanePenaltyChanged); - aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Vehicle_restrictions_penalty"), 0f, 1000f, 25f, globalConf.VehicleRestrictionsPenalty, onVehicleRestrictionsPenaltyChanged); + panelHelper = new UIHelper(currentPanel); - var aiLaneChangeParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_parameters")); + var vehBehaviorGroup = panelHelper.AddGroup(Translation.GetString("Vehicle_behavior")); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("U-turn_lane_distance"), 1f, 5f, 1f, globalConf.UturnLaneDistance, onUturnLaneDistanceChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Incompatible_lane_distance"), 1f, 5f, 1f, globalConf.IncompatibleLaneDistance, onIncompatibleLaneDistanceChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Lane_changing_randomization_modulo"), 1f, 100f, 1f, globalConf.RandomizedLaneChangingModulo, onRandomizedLaneChangingModuloChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Min._controlled_segments_in_front_of_highway_interchanges"), 1f, 10f, 1f, globalConf.MinHighwayInterchangeSegments, onMinHighwayInterchangeSegmentsChanged); - aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Max._controlled_segments_in_front_of_highway_interchanges"), 1f, 30f, 1f, globalConf.MaxHighwayInterchangeSegments, onMaxHighwayInterchangeSegmentsChanged); + recklessDriversDropdown = vehBehaviorGroup.AddDropdown(Translation.GetString("Reckless_driving") + ":", new string[] { Translation.GetString("Path_Of_Evil_(10_%)"), Translation.GetString("Rush_Hour_(5_%)"), Translation.GetString("Minor_Complaints_(2_%)"), Translation.GetString("Holy_City_(0_%)") }, recklessDrivers, onRecklessDriversChanged) as UIDropDown; + recklessDriversDropdown.width = 300; + individualDrivingStyleToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Individual_driving_styles"), individualDrivingStyle, onIndividualDrivingStyleChanged) as UICheckBox; - var aiLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_cost_factors")); + if (SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { + strongerRoadConditionEffectsToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Road_condition_has_a_bigger_impact_on_vehicle_speed"), strongerRoadConditionEffects, onStrongerRoadConditionEffectsChanged) as UICheckBox; + } + disableDespawningToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Disable_despawning"), disableDespawning, onDisableDespawningChanged) as UICheckBox; - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_city_roads"), 1f, 5f, 0.05f, globalConf.CityRoadLaneChangingBaseCost, onCityRoadLaneChangingBaseCostChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_highways"), 1f, 5f, 0.05f, globalConf.HighwayLaneChangingBaseCost, onHighwayLaneChangingBaseCostChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("In_front_of_highway_interchanges"), 1f, 5f, 0.05f, globalConf.HighwayInterchangeLaneChangingBaseCost, onHighwayInterchangeLaneChangingBaseCostChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("For_heavy_vehicles"), 1f, 5f, 0.05f, globalConf.HeavyVehicleLaneChangingCostFactor, onHeavyVehicleLaneChangingCostFactorChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_congested_roads"), 1f, 5f, 0.05f, globalConf.CongestionLaneChangingCostFactor, onCongestionLaneChangingCostFactorChanged); - aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("When_changing_multiple_lanes_at_once"), 1f, 5f, 0.05f, globalConf.MoreThanOneLaneChangingCostFactor, onMoreThanOneLaneChangingCostFactorChanged); + var vehAiGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI")); + advancedAIToggle = vehAiGroup.AddCheckbox(Translation.GetString("Enable_Advanced_Vehicle_AI"), advancedAI, onAdvancedAIChanged) as UICheckBox; + altLaneSelectionRatioSlider = vehAiGroup.AddSlider(Translation.GetString("Dynamic_lane_section") + ":", 0, 100, 5, altLaneSelectionRatio, onAltLaneSelectionRatioChanged) as UISlider; + altLaneSelectionRatioSlider.parent.Find("Label").width = 450; - var aiParkingLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI") + ": " + Translation.GetString("General")); - */ + var parkAiGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI")); + prohibitPocketCarsToggle = parkAiGroup.AddCheckbox(Translation.GetString("Enable_more_realistic_parking"), prohibitPocketCars, onProhibitPocketCarsChanged) as UICheckBox; - // DEBUG - /*++tabIndex; + var ptGroup = panelHelper.AddGroup(Translation.GetString("Public_transport")); + realisticPublicTransportToggle = ptGroup.AddCheckbox(Translation.GetString("Prevent_excessive_transfers_at_public_transport_stations"), realisticPublicTransport, onRealisticPublicTransportChanged) as UICheckBox; + } - settingsButton = tabStrip.AddTab("Debug", tabTemplate, true); - settingsButton.textPadding = new RectOffset(10, 10, 10, 10); - settingsButton.autoSize = true; - settingsButton.tooltip = "Debug"; + private static void MakeSettings_General(UITabstrip tabStrip, int tabIndex) { + AddOptionTab(tabStrip, Translation.GetString("General"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); + tabStrip.selectedIndex = tabIndex; - currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; + UIPanel currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; - panelHelper = new UIHelper(currentPanel); + UIHelper panelHelper = new UIHelper(currentPanel); + + var generalGroup = panelHelper.AddGroup(Translation.GetString("General")); - debugSwitchFields.Clear(); - for (int i = 0; i < Debug.Switches.Length; ++i) { - int index = i; - string varName = $"Debug switch #{i}"; - debugSwitchFields.Add(panelHelper.AddCheckbox(varName, Debug.Switches[i], delegate (bool newVal) { onBoolValueChanged(varName, newVal, ref Debug.Switches[index]); }) as UICheckBox); + string[] languageLabels = new string[Translation.AVAILABLE_LANGUAGE_CODES.Count + 1]; + languageLabels[0] = Translation.GetString("Game_language"); + for (int i = 0; i < Translation.AVAILABLE_LANGUAGE_CODES.Count; ++i) { + languageLabels[i + 1] = Translation.LANGUAGE_LABELS[Translation.AVAILABLE_LANGUAGE_CODES[i]]; + } + int languageIndex = 0; + string curLangCode = GlobalConfig.Instance.LanguageCode; + if (curLangCode != null) { + languageIndex = Translation.AVAILABLE_LANGUAGE_CODES.IndexOf(curLangCode); + if (languageIndex < 0) { + languageIndex = 0; + } else { + ++languageIndex; + } } - debugValueFields.Clear(); - for (int i = 0; i < debugValues.Length; ++i) { - int index = i; - string varName = $"Debug value #{i}"; - debugValueFields.Add(panelHelper.AddTextfield(varName, String.Format("{0:0.##}", debugValues[i]), delegate(string newValStr) { onFloatValueChanged(varName, newValStr, ref debugValues[index]); }, null) as UITextField); - }*/ -#endif + languageDropdown = generalGroup.AddDropdown(Translation.GetString("Language") + ":", languageLabels, languageIndex, onLanguageChanged) as UIDropDown; + lockButtonToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_button_position"), GlobalConfig.Instance.Main.MainMenuButtonPosLocked, onLockButtonChanged) as UICheckBox; + lockMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_position"), GlobalConfig.Instance.Main.MainMenuPosLocked, onLockMenuChanged) as UICheckBox; + tinyMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Compact_main_menu"), GlobalConfig.Instance.Main.TinyMainMenu, onTinyMenuChanged) as UICheckBox; + guiTransparencySlider = generalGroup.AddSlider(Translation.GetString("Window_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.GuiTransparency, onGuiTransparencyChanged) as UISlider; + guiTransparencySlider.parent.Find("Label").width = 500; + overlayTransparencySlider = generalGroup.AddSlider(Translation.GetString("Overlay_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.OverlayTransparency, onOverlayTransparencyChanged) as UISlider; + overlayTransparencySlider.parent.Find("Label").width = 500; + enableTutorialToggle = generalGroup.AddCheckbox(Translation.GetString("Enable_tutorial_messages"), GlobalConfig.Instance.Main.EnableTutorial, onEnableTutorialsChanged) as UICheckBox; + showCompatibilityCheckErrorToggle = generalGroup.AddCheckbox(Translation.GetString("Notify_me_if_there_is_an_unexpected_mod_conflict"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; + scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; + ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; + Indent(ignoreDisabledModsToggle); - tabStrip.selectedIndex = 0; + // General: Speed Limits + setupSpeedLimitsPanel(panelHelper, generalGroup); + + // General: Simulation + var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); + simAccuracyDropdown = simGroup.AddDropdown(Translation.GetString("Simulation_accuracy") + ":", new string[] { Translation.GetString("Very_high"), Translation.GetString("High"), Translation.GetString("Medium"), Translation.GetString("Low"), Translation.GetString("Very_Low") }, simAccuracy, onSimAccuracyChanged) as UIDropDown; + instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; } private static void setupSpeedLimitsPanel(UIHelper panelHelper, UIHelperBase generalGroup) { diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 0e5219994..2c5836dbf 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -176,6 +176,7 @@ + diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 09cc9f43a..6908deb0e 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -1,51 +1,50 @@ using CSUtil.Commons; using ICities; using System.Reflection; -using System.Runtime.CompilerServices; -using ColossalFramework; using ColossalFramework.UI; using TrafficManager.State; using TrafficManager.Util; -using UnityEngine; namespace TrafficManager { - public class TrafficManagerMod : IUserMod { + public class TrafficManagerMod : IUserMod { - public static readonly string Version = "10.20"; + public static readonly string Version = "10.20"; - public static readonly uint GameVersion = 184673552u; - public static readonly uint GameVersionA = 1u; - public static readonly uint GameVersionB = 12u; - public static readonly uint GameVersionC = 0u; - public static readonly uint GameVersionBuild = 5u; + public static readonly uint GameVersion = 184673552u; + public static readonly uint GameVersionA = 1u; + public static readonly uint GameVersionB = 12u; + public static readonly uint GameVersionC = 0u; + public static readonly uint GameVersionBuild = 5u; - public string Name => "TM:PE " + Version; + public string Name => "TM:PE " + Version; - public string Description => "Manage your city's traffic"; + public string Description => "Manage your city's traffic"; - public void OnEnabled() { - Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); - if (UIView.GetAView() != null) { - OnGameIntroLoaded(); - } else { - LoadingManager.instance.m_introLoaded += OnGameIntroLoaded; - } - } + public void OnEnabled() { + Log.Info($"TM:PE enabled. Version {Version}, " + + $"Build {Assembly.GetExecutingAssembly().GetName().Version} " + + $"for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); + if (UIView.GetAView() != null) { + OnGameIntroLoaded(); + } else { + LoadingManager.instance.m_introLoaded += OnGameIntroLoaded; + } + } - public void OnDisabled() { - Log.Info("TM:PE disabled."); - LoadingManager.instance.m_introLoaded -= OnGameIntroLoaded; - } + public void OnDisabled() { + Log.Info("TM:PE disabled."); + LoadingManager.instance.m_introLoaded -= OnGameIntroLoaded; + } - public void OnSettingsUI(UIHelperBase helper) { - Options.makeSettings(helper); - } + public void OnSettingsUI(UIHelperBase helper) { + Options.MakeSettings(helper); + } - private static void OnGameIntroLoaded() { + private static void OnGameIntroLoaded() { if (GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { ModsCompatibilityChecker mcc = new ModsCompatibilityChecker(); mcc.PerformModCheck(); } - } - } -} + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/LinearSpriteButton.cs b/TLM/TLM/UI/LinearSpriteButton.cs index d9ada8abb..268022705 100644 --- a/TLM/TLM/UI/LinearSpriteButton.cs +++ b/TLM/TLM/UI/LinearSpriteButton.cs @@ -1,127 +1,156 @@ -using ColossalFramework.Math; -using ColossalFramework.UI; +using ColossalFramework.UI; using CSUtil.Commons; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using ColossalFramework; -using TrafficManager.State; +using TrafficManager.State.Keybinds; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI.MainMenu { - public abstract class LinearSpriteButton : UIButton { - public enum ButtonMouseState { - Base, - Hovered, - MouseDown - } - - public const string MENU_BUTTON_BACKGROUND = "Bg"; - public const string MENU_BUTTON_FOREGROUND = "Fg"; - - public const string MENU_BUTTON_BASE = "Base"; - public const string MENU_BUTTON_HOVERED = "Hovered"; - public const string MENU_BUTTON_MOUSEDOWN = "MouseDown"; - - public const string MENU_BUTTON_DEFAULT = "Default"; - public const string MENU_BUTTON_ACTIVE = "Active"; - - protected static string GetButtonBackgroundTextureId(string prefix, ButtonMouseState state, bool active) { - string ret = prefix + MENU_BUTTON_BACKGROUND; - - switch (state) { - case ButtonMouseState.Base: - ret += MENU_BUTTON_BASE; - break; - case ButtonMouseState.Hovered: - ret += MENU_BUTTON_HOVERED; - break; - case ButtonMouseState.MouseDown: - ret += MENU_BUTTON_MOUSEDOWN; - break; - } - - ret += active ? MENU_BUTTON_ACTIVE : MENU_BUTTON_DEFAULT; - return ret; - } - - protected static string GetButtonForegroundTextureId(string prefix, string function, bool active) { - string ret = prefix + MENU_BUTTON_FOREGROUND + function; - ret += active ? MENU_BUTTON_ACTIVE : MENU_BUTTON_DEFAULT; - return ret; - } - - public abstract bool CanActivate(); - public abstract string ButtonName { get; } - public abstract string FunctionName { get; } - public abstract string[] FunctionNames { get; } - public abstract Texture2D AtlasTexture { get; } - - public abstract int Width { get; } - public abstract int Height { get; } - - public override void Start() { - string[] textureIds = new string[Enum.GetValues(typeof(ButtonMouseState)).Length * (CanActivate() ? 2 : 1) + FunctionNames.Length * 2]; - - int i = 0; - foreach (ButtonMouseState mouseState in EnumUtil.GetValues()) { - if (CanActivate()) { - textureIds[i++] = GetButtonBackgroundTextureId(ButtonName, mouseState, true); - } - textureIds[i++] = GetButtonBackgroundTextureId(ButtonName, mouseState, false); - } - - foreach (string function in FunctionNames) { - textureIds[i++] = GetButtonForegroundTextureId(ButtonName, function, false); - } - - foreach (string function in FunctionNames) { - textureIds[i++] = GetButtonForegroundTextureId(ButtonName, function, true); - } - - // Set the atlases for background/foreground - atlas = TextureUtil.GenerateLinearAtlas("TMPE_" + ButtonName + "Atlas", AtlasTexture, textureIds.Length, textureIds); - - m_ForegroundSpriteMode = UIForegroundSpriteMode.Scale; - UpdateProperties(); - - // Enable button sounds. - playAudioEvents = true; - } - - public abstract bool Active { get; } - public abstract string Tooltip { get; } - public abstract bool Visible { get; } - public abstract void HandleClick(UIMouseEventParameter p); - - public virtual SavedInputKey ShortcutKey { - get { return null; } - } - - protected override void OnClick(UIMouseEventParameter p) { - HandleClick(p); - UpdateProperties(); - } - - internal void UpdateProperties() { - bool active = CanActivate() ? Active : false; - - m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.Base, active); - m_BackgroundSprites.m_Hovered = GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.Hovered, active); - m_PressedBgSprite = GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.MouseDown, active); - - m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = GetButtonForegroundTextureId(ButtonName, FunctionName, active); - m_ForegroundSprites.m_Hovered = m_PressedFgSprite = GetButtonForegroundTextureId(ButtonName, FunctionName, true); - - var shortcutText = ShortcutKey == null - ? string.Empty - : "\n" + ShortcutKey.ToLocalizedString("KEYNAME"); - tooltip = Translation.GetString(Tooltip) + shortcutText; - - isVisible = Visible; - this.Invalidate(); - } - } -} + public abstract class LinearSpriteButton : UIButton { + public enum ButtonMouseState { + Base, + Hovered, + MouseDown + } + + public const string MENU_BUTTON_BACKGROUND = "Bg"; + public const string MENU_BUTTON_FOREGROUND = "Fg"; + + public const string MENU_BUTTON_BASE = "Base"; + public const string MENU_BUTTON_HOVERED = "Hovered"; + public const string MENU_BUTTON_MOUSEDOWN = "MouseDown"; + + public const string MENU_BUTTON_DEFAULT = "Default"; + public const string MENU_BUTTON_ACTIVE = "Active"; + + protected static string GetButtonBackgroundTextureId(string prefix, ButtonMouseState state, bool active) { + string ret = prefix + MENU_BUTTON_BACKGROUND; + + switch (state) { + case ButtonMouseState.Base: + ret += MENU_BUTTON_BASE; + break; + case ButtonMouseState.Hovered: + ret += MENU_BUTTON_HOVERED; + break; + case ButtonMouseState.MouseDown: + ret += MENU_BUTTON_MOUSEDOWN; + break; + } + + ret += active ? MENU_BUTTON_ACTIVE : MENU_BUTTON_DEFAULT; + return ret; + } + + protected static string GetButtonForegroundTextureId(string prefix, string function, bool active) { + string ret = prefix + MENU_BUTTON_FOREGROUND + function; + ret += active ? MENU_BUTTON_ACTIVE : MENU_BUTTON_DEFAULT; + return ret; + } + + public abstract bool CanActivate(); + + public abstract string ButtonName { get; } + + public abstract string FunctionName { get; } + + public abstract string[] FunctionNames { get; } + + public abstract Texture2D AtlasTexture { get; } + + public abstract int Width { get; } + public abstract int Height { get; } + + public override void Start() { + var textureCount = Enum.GetValues(typeof(ButtonMouseState)).Length * (CanActivate() ? 2 : 1) + + FunctionNames.Length * 2; + string[] textureIds = new string[textureCount]; + + int i = 0; + foreach (ButtonMouseState mouseState in EnumUtil.GetValues()) { + if (CanActivate()) { + textureIds[i++] = GetButtonBackgroundTextureId(ButtonName, mouseState, true); + } + textureIds[i++] = GetButtonBackgroundTextureId(ButtonName, mouseState, false); + } + + foreach (string function in FunctionNames) { + textureIds[i++] = GetButtonForegroundTextureId(ButtonName, function, false); + } + + foreach (string function in FunctionNames) { + textureIds[i++] = GetButtonForegroundTextureId(ButtonName, function, true); + } + + // Set the atlases for background/foreground + atlas = TextureUtil.GenerateLinearAtlas("TMPE_" + ButtonName + "Atlas", AtlasTexture, textureIds.Length, textureIds); + + m_ForegroundSpriteMode = UIForegroundSpriteMode.Scale; + UpdateProperties(); + + // Enable button sounds. + playAudioEvents = true; + } + + public abstract bool Active { get; } + + public abstract string Tooltip { get; } + + public abstract bool Visible { get; } + + public abstract void HandleClick(UIMouseEventParameter p); + + public virtual SavedInputKey ShortcutKey { + get { return null; } + } + + protected override void OnClick(UIMouseEventParameter p) { + HandleClick(p); + UpdateProperties(); + } + + internal void UpdateProperties() { + bool active = CanActivate() ? Active : false; + + m_BackgroundSprites.m_Normal = + m_BackgroundSprites.m_Disabled = + m_BackgroundSprites.m_Focused = + GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.Base, active); + + m_BackgroundSprites.m_Hovered = + GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.Hovered, active); + + m_PressedBgSprite = + GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.MouseDown, active); + + m_ForegroundSprites.m_Normal = + m_ForegroundSprites.m_Disabled = + m_ForegroundSprites.m_Focused = + GetButtonForegroundTextureId(ButtonName, FunctionName, active); + + m_ForegroundSprites.m_Hovered = + m_PressedFgSprite = + GetButtonForegroundTextureId(ButtonName, FunctionName, true); + + var shortcutText = GetShortcutTooltip(); + tooltip = Translation.GetString(Tooltip) + shortcutText; + + isVisible = Visible; + this.Invalidate(); + } + + + /// + /// If shortcut key was set to a non-empty something, then form a text tooltip, + /// otherwise an empty string is returned. + /// + /// Tooltip to append to the main tooltip text, or an empty string + private string GetShortcutTooltip() { + var isNotSet = ShortcutKey == null || Keybind.IsEmpty(ShortcutKey.value); + return isNotSet + ? string.Empty + : "\n" + Keybind.Str(ShortcutKey); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index 716c32919..131247609 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -51,7 +51,7 @@ public class SizeProfile { public static readonly SizeProfile[] SIZE_PROFILES = { - new SizeProfile() { + new SizeProfile { NUM_BUTTONS_PER_ROW = 6, NUM_ROWS = 2, @@ -63,7 +63,7 @@ public static readonly SizeProfile[] SIZE_PROFILES MENU_WIDTH = 215, MENU_HEIGHT = 95 }, - new SizeProfile() { + new SizeProfile { NUM_BUTTONS_PER_ROW = 6, NUM_ROWS = 2, diff --git a/TLM/TLM/UI/MainMenu/MenuButton.cs b/TLM/TLM/UI/MainMenu/MenuButton.cs index 96fac43a9..f83b651d9 100644 --- a/TLM/TLM/UI/MainMenu/MenuButton.cs +++ b/TLM/TLM/UI/MainMenu/MenuButton.cs @@ -1,89 +1,63 @@ -using ColossalFramework.Math; -using ColossalFramework.UI; -using CSUtil.Commons; +using ColossalFramework.UI; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.State; -using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI.MainMenu { - public abstract class MenuButton : LinearSpriteButton { - public enum ButtonFunction { - LaneConnector, - ClearTraffic, - DespawnDisabled, - DespawnEnabled, - JunctionRestrictions, - LaneArrows, - ManualTrafficLights, - PrioritySigns, - SpeedLimits, - TimedTrafficLights, - ToggleTrafficLights, - VehicleRestrictions, - ParkingRestrictions - } - - public const string MENU_BUTTON = "TMPE_MenuButton"; - public const int BUTTON_SIZE = 30; - public override void HandleClick(UIMouseEventParameter p) { } - - protected override void OnClick(UIMouseEventParameter p) { - OnClickInternal(p); - foreach (MenuButton button in LoadingExtension.BaseUI.MainMenu.Buttons) { - button.UpdateProperties(); - } - } - - public abstract void OnClickInternal(UIMouseEventParameter p); - public abstract ButtonFunction Function { get; } - - public override bool CanActivate() { - return true; - } - - public override string ButtonName { - get { - return MENU_BUTTON; - } - } - - public override string FunctionName { - get { - return Function.ToString(); - } - } - - public override string[] FunctionNames { - get { - var functions = Enum.GetValues(typeof(ButtonFunction)); - string[] ret = new string[functions.Length]; - for (int i = 0; i < functions.Length; ++i) { - ret[i] = functions.GetValue(i).ToString(); - } - return ret; - } - } - - public override Texture2D AtlasTexture { - get { - return TextureResources.MainMenuButtonsTexture2D; - } - } - - public override int Width { - get { - return 50; - } - } - - public override int Height { - get { - return 50; - } - } - } -} + public abstract class MenuButton : LinearSpriteButton { + public enum ButtonFunction { + LaneConnector, + ClearTraffic, + DespawnDisabled, + DespawnEnabled, + JunctionRestrictions, + LaneArrows, + ManualTrafficLights, + PrioritySigns, + SpeedLimits, + TimedTrafficLights, + ToggleTrafficLights, + VehicleRestrictions, + ParkingRestrictions + } + + private const string MENU_BUTTON = "TMPE_MenuButton"; + + public override void HandleClick(UIMouseEventParameter p) { } + + protected override void OnClick(UIMouseEventParameter p) { + OnClickInternal(p); + foreach (MenuButton button in LoadingExtension.BaseUI.MainMenu.Buttons) { + button.UpdateProperties(); + } + } + + public abstract void OnClickInternal(UIMouseEventParameter p); + + public abstract ButtonFunction Function { get; } + + public override bool CanActivate() { + return true; + } + + public override string ButtonName => MENU_BUTTON; + + public override string FunctionName => Function.ToString(); + + public override string[] FunctionNames { + get { + var functions = Enum.GetValues(typeof(ButtonFunction)); + string[] ret = new string[functions.Length]; + for (int i = 0; i < functions.Length; ++i) { + ret[i] = functions.GetValue(i).ToString(); + } + return ret; + } + } + + public override Texture2D AtlasTexture => TextureResources.MainMenuButtonsTexture2D; + + public override int Width => 50; + + public override int Height => 50; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/VersionLabel.cs b/TLM/TLM/UI/MainMenu/VersionLabel.cs index 4434266e6..a511787bf 100644 --- a/TLM/TLM/UI/MainMenu/VersionLabel.cs +++ b/TLM/TLM/UI/MainMenu/VersionLabel.cs @@ -1,17 +1,15 @@ using ColossalFramework.UI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using UnityEngine; namespace TrafficManager.UI.MainMenu { - public class VersionLabel : UILabel { - public override void Start() { - size = new Vector2(MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH, MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER); // TODO use current size profile - text = "TM:PE " + TrafficManagerMod.Version; - relativePosition = new Vector3(5f, 5f); - textAlignment = UIHorizontalAlignment.Left; - } - } -} + public class VersionLabel : UILabel { + public override void Start() { + // TODO use current size profile + size = new Vector2(MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH, + MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER); + text = "TM:PE " + TrafficManagerMod.Version; + relativePosition = new Vector3(5f, 5f); + textAlignment = UIHorizontalAlignment.Left; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/UIBase.cs b/TLM/TLM/UI/UIBase.cs index 4f1d48bf6..33d0b7123 100644 --- a/TLM/TLM/UI/UIBase.cs +++ b/TLM/TLM/UI/UIBase.cs @@ -56,24 +56,27 @@ public bool IsVisible() { } public void ToggleMainMenu() { - if (IsVisible()) + if (IsVisible()) { Close(); - else + } else { Show(); + } } internal void RebuildMenu() { Close(); if (MainMenu != null) { CustomKeyHandler keyHandler = MainMenu.GetComponent(); - if(keyHandler != null) + if(keyHandler != null) { UnityEngine.Object.Destroy(keyHandler); + } UnityEngine.Object.Destroy(MainMenu); #if DEBUG UnityEngine.Object.Destroy(DebugMenu); #endif } + var uiView = UIView.GetAView(); MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); MainMenu.gameObject.AddComponent(); @@ -86,7 +89,7 @@ public void Show() { try { ToolsModifierControl.mainToolbar.CloseEverything(); } catch (Exception e) { - Log.Error("Error on Show(): " + e.ToString()); + Log.Error("Error on Show(): " + e); } foreach (var button in GetMenu().Buttons) { @@ -115,6 +118,7 @@ public void Close() { if (tmTool != null) { tmTool.SetToolMode(UI.ToolMode.None); } + SetToolMode(TrafficManagerMode.None); _uiShown = false; MainMenuButton.UpdateSprites(); @@ -172,8 +176,9 @@ private static void DestroyTool() { UnityEngine.Object.Destroy(tool); tool = null; } - } else + } else { Log.Warning("LoadingExtensions.DestroyTool: ToolsModifierControl.toolController is null!"); + } } } } \ No newline at end of file diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 2d17c5f80..37b5e8c46 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -1,140 +1,157 @@ -using ColossalFramework.Math; -using ColossalFramework.UI; +using ColossalFramework.UI; using CSUtil.Commons; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using TrafficManager.State; using TrafficManager.State.Keybinds; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI { - public class UIMainMenuButton : UIButton, IObserver { - public const string MAIN_MENU_BUTTON_BG_BASE = "TMPE_MainMenuButtonBgBase"; - public const string MAIN_MENU_BUTTON_BG_HOVERED = "TMPE_MainMenuButtonBgHovered"; - public const string MAIN_MENU_BUTTON_BG_ACTIVE = "TMPE_MainMenuButtonBgActive"; - public const string MAIN_MENU_BUTTON_FG_BASE = "TMPE_MainMenuButtonFgBase"; - public const string MAIN_MENU_BUTTON_FG_HOVERED = "TMPE_MainMenuButtonFgHovered"; - public const string MAIN_MENU_BUTTON_FG_ACTIVE = "TMPE_MainMenuButtonFgActive"; - - public const int BUTTON_WIDTH = 50; - public const int BUTTON_HEIGHT = 50; - - public UIDragHandle Drag { get; private set; } - - IDisposable confDisposable; - - public override void Start() { - // Place the button. - OnUpdate(GlobalConfig.Instance); - - confDisposable = GlobalConfig.Instance.Subscribe(this); - - // Set the atlas and background/foreground - atlas = TextureUtil.GenerateLinearAtlas("TMPE_MainMenuButtonAtlas", TextureResources.MainMenuButtonTexture2D, 6, new string[] { - MAIN_MENU_BUTTON_BG_BASE, - MAIN_MENU_BUTTON_BG_HOVERED, - MAIN_MENU_BUTTON_BG_ACTIVE, - MAIN_MENU_BUTTON_FG_BASE, - MAIN_MENU_BUTTON_FG_HOVERED, - MAIN_MENU_BUTTON_FG_ACTIVE - }); - - UpdateSprites(); - - // Set the button dimensions. - width = BUTTON_WIDTH; - height = BUTTON_HEIGHT; - - // Enable button sounds. - playAudioEvents = true; - - var dragHandler = new GameObject("TMPE_MainButton_DragHandler"); - dragHandler.transform.parent = transform; - dragHandler.transform.localPosition = Vector3.zero; - Drag = dragHandler.AddComponent(); - - Drag.width = width; - Drag.height = height; - Drag.enabled = !GlobalConfig.Instance.Main.MainMenuButtonPosLocked; - } - - public override void OnDestroy() { - if (confDisposable != null) { - confDisposable.Dispose(); - } - } - - internal void SetPosLock(bool lck) { - Drag.enabled = !lck; - } - - protected override void OnClick(UIMouseEventParameter p) { - Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); - LoadingExtension.BaseUI.ToggleMainMenu(); - UpdateSprites(); - } - - protected override void OnPositionChanged() { - GlobalConfig config = GlobalConfig.Instance; - - bool posChanged = (config.Main.MainMenuButtonX != (int)absolutePosition.x || config.Main.MainMenuButtonY != (int)absolutePosition.y); - - if (posChanged) { - Log._Debug($"Button position changed to {absolutePosition.x}|{absolutePosition.y}"); - - config.Main.MainMenuButtonX = (int)absolutePosition.x; - config.Main.MainMenuButtonY = (int)absolutePosition.y; - - GlobalConfig.WriteConfig(); - } - base.OnPositionChanged(); - } - - internal void UpdateSprites() { - if (! LoadingExtension.BaseUI.IsVisible()) { - m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = MAIN_MENU_BUTTON_BG_BASE; - m_BackgroundSprites.m_Hovered = MAIN_MENU_BUTTON_BG_HOVERED; - m_PressedBgSprite = MAIN_MENU_BUTTON_BG_ACTIVE; - - m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = MAIN_MENU_BUTTON_FG_BASE; - m_ForegroundSprites.m_Hovered = MAIN_MENU_BUTTON_FG_HOVERED; - m_PressedFgSprite = MAIN_MENU_BUTTON_FG_ACTIVE; - } else { - m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = m_BackgroundSprites.m_Hovered = MAIN_MENU_BUTTON_BG_ACTIVE; - m_PressedBgSprite = MAIN_MENU_BUTTON_BG_HOVERED; - - m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = m_ForegroundSprites.m_Hovered = MAIN_MENU_BUTTON_FG_ACTIVE; - m_PressedFgSprite = MAIN_MENU_BUTTON_FG_HOVERED; - } - this.Invalidate(); - } - - public void OnUpdate(GlobalConfig config) { - UpdatePosition(new Vector2(config.Main.MainMenuButtonX, config.Main.MainMenuButtonY)); - } - - public void UpdatePosition(Vector2 pos) { - Rect rect = new Rect(pos.x, pos.y, BUTTON_WIDTH, BUTTON_HEIGHT); - Vector2 resolution = UIView.GetAView().GetScreenResolution(); - VectorUtil.ClampRectToScreen(ref rect, resolution); - Log.Info($"Setting main menu button position to [{pos.x},{pos.y}]"); - absolutePosition = rect.position; - Invalidate(); - } - - public void OnGUI() { - if (!UIView.HasModalInput() - && !UIView.HasInputFocus() - && KeymappingSettings.KeyToggleTMPEMainMenu.IsPressed(Event.current)) { - LoadingExtension.BaseUI?.ToggleMainMenu(); - } - - // FIXME: Tooltip text is not displayed on the tool button - // var shortcutText = OptionsKeymapping.KeyToggleTMPEMainMenu.ToLocalizedString("KEYNAME"); - // tooltip = Translation.GetString("Keybind_toggle_TMPE_main_menu") + shortcutText; - } - } -} + public class UIMainMenuButton : UIButton, IObserver { + private const string MAIN_MENU_BUTTON_BG_BASE = "TMPE_MainMenuButtonBgBase"; + private const string MAIN_MENU_BUTTON_BG_HOVERED = "TMPE_MainMenuButtonBgHovered"; + private const string MAIN_MENU_BUTTON_BG_ACTIVE = "TMPE_MainMenuButtonBgActive"; + private const string MAIN_MENU_BUTTON_FG_BASE = "TMPE_MainMenuButtonFgBase"; + private const string MAIN_MENU_BUTTON_FG_HOVERED = "TMPE_MainMenuButtonFgHovered"; + private const string MAIN_MENU_BUTTON_FG_ACTIVE = "TMPE_MainMenuButtonFgActive"; + + private const int BUTTON_WIDTH = 50; + private const int BUTTON_HEIGHT = 50; + + private UIDragHandle Drag { get; set; } + + IDisposable confDisposable; + + public override void Start() { + // Place the button. + OnUpdate(GlobalConfig.Instance); + + confDisposable = GlobalConfig.Instance.Subscribe(this); + + // Set the atlas and background/foreground + var spriteNames = new[] { + MAIN_MENU_BUTTON_BG_BASE, + MAIN_MENU_BUTTON_BG_HOVERED, + MAIN_MENU_BUTTON_BG_ACTIVE, + MAIN_MENU_BUTTON_FG_BASE, + MAIN_MENU_BUTTON_FG_HOVERED, + MAIN_MENU_BUTTON_FG_ACTIVE + }; + atlas = TextureUtil.GenerateLinearAtlas( + "TMPE_MainMenuButtonAtlas", + TextureResources.MainMenuButtonTexture2D, + 6, + spriteNames); + + UpdateSprites(); + + // Set the button dimensions. + width = BUTTON_WIDTH; + height = BUTTON_HEIGHT; + + // Enable button sounds. + playAudioEvents = true; + + var dragHandler = new GameObject("TMPE_MainButton_DragHandler"); + dragHandler.transform.parent = transform; + dragHandler.transform.localPosition = Vector3.zero; + Drag = dragHandler.AddComponent(); + + Drag.width = width; + Drag.height = height; + Drag.enabled = !GlobalConfig.Instance.Main.MainMenuButtonPosLocked; + + // Set up the tooltip + var uiView = GetUIView(); + if (uiView != null) { + m_TooltipBox = uiView.defaultTooltipBox; + } + + tooltip = Translation.GetString("Keybind_toggle_TMPE_main_menu") + + GetTooltip(); + } + + public override void OnDestroy() { + if (confDisposable != null) { + confDisposable.Dispose(); + } + } + + internal void SetPosLock(bool lck) { + Drag.enabled = !lck; + } + + protected override void OnClick(UIMouseEventParameter p) { + Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); + LoadingExtension.BaseUI.ToggleMainMenu(); + UpdateSprites(); + } + + protected override void OnPositionChanged() { + GlobalConfig config = GlobalConfig.Instance; + + bool posChanged = config.Main.MainMenuButtonX != (int)absolutePosition.x + || config.Main.MainMenuButtonY != (int)absolutePosition.y; + + if (posChanged) { + Log._Debug($"Button position changed to {absolutePosition.x}|{absolutePosition.y}"); + + config.Main.MainMenuButtonX = (int)absolutePosition.x; + config.Main.MainMenuButtonY = (int)absolutePosition.y; + + GlobalConfig.WriteConfig(); + } + base.OnPositionChanged(); + } + + internal void UpdateSprites() { + if (! LoadingExtension.BaseUI.IsVisible()) { + m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = MAIN_MENU_BUTTON_BG_BASE; + m_BackgroundSprites.m_Hovered = MAIN_MENU_BUTTON_BG_HOVERED; + m_PressedBgSprite = MAIN_MENU_BUTTON_BG_ACTIVE; + + m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = MAIN_MENU_BUTTON_FG_BASE; + m_ForegroundSprites.m_Hovered = MAIN_MENU_BUTTON_FG_HOVERED; + m_PressedFgSprite = MAIN_MENU_BUTTON_FG_ACTIVE; + } else { + m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = m_BackgroundSprites.m_Hovered = MAIN_MENU_BUTTON_BG_ACTIVE; + m_PressedBgSprite = MAIN_MENU_BUTTON_BG_HOVERED; + + m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = m_ForegroundSprites.m_Hovered = MAIN_MENU_BUTTON_FG_ACTIVE; + m_PressedFgSprite = MAIN_MENU_BUTTON_FG_HOVERED; + } + this.Invalidate(); + } + + public void OnUpdate(GlobalConfig config) { + UpdatePosition(new Vector2(config.Main.MainMenuButtonX, config.Main.MainMenuButtonY)); + } + + public void UpdatePosition(Vector2 pos) { + Rect rect = new Rect(pos.x, pos.y, BUTTON_WIDTH, BUTTON_HEIGHT); + Vector2 resolution = UIView.GetAView().GetScreenResolution(); + VectorUtil.ClampRectToScreen(ref rect, resolution); + Log.Info($"Setting main menu button position to [{pos.x},{pos.y}]"); + absolutePosition = rect.position; + Invalidate(); + } + + public void OnGUI() { + if (!UIView.HasModalInput() + && !UIView.HasInputFocus() + && KeymappingSettings.KeyToggleTMPEMainMenu.IsPressed(Event.current)) { + if (LoadingExtension.BaseUI != null) { + LoadingExtension.BaseUI.ToggleMainMenu(); + } + } + } + + private string GetTooltip() { + if (Keybind.IsEmpty(KeymappingSettings.KeyToggleTMPEMainMenu)) { + return string.Empty; + } + + return "\n" + Keybind.Str(KeymappingSettings.KeyToggleTMPEMainMenu); + } + } +} \ No newline at end of file From b337cd4eb06d5dba410c3cea1bfea208b212a445 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sat, 29 Jun 2019 14:35:08 +0200 Subject: [PATCH 103/142] Refactored keybinds editor to allow a second keybind per setting (WIP) --- TLM/TLM/State/Keybinds/Keybind.cs | 55 +++ TLM/TLM/State/Keybinds/KeybindSetting.cs | 99 +++++ ...pingSettings.cs => KeybindSettingsBase.cs} | 355 +++++++----------- TLM/TLM/State/Keybinds/KeybindSettingsPage.cs | 35 ++ .../State/Keybinds/KeymappingSettingsMain.cs | 35 -- TLM/TLM/State/Keybinds/TmpeRebindableKey.cs | 15 - TLM/TLM/State/Options.cs | 2 +- TLM/TLM/TLM.csproj | 6 +- TLM/TLM/UI/LinearSpriteButton.cs | 7 +- .../UI/MainMenu/JunctionRestrictionsButton.cs | 2 +- TLM/TLM/UI/MainMenu/LaneArrowsButton.cs | 2 +- TLM/TLM/UI/MainMenu/LaneConnectorButton.cs | 2 +- TLM/TLM/UI/MainMenu/MainMenuPanel.cs | 12 +- TLM/TLM/UI/MainMenu/PrioritySignsButton.cs | 2 +- TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs | 2 +- .../UI/MainMenu/ToggleTrafficLightsButton.cs | 2 +- TLM/TLM/UI/SubTools/LaneConnectorTool.cs | 4 +- TLM/TLM/UI/UIMainMenuButton.cs | 15 +- 18 files changed, 362 insertions(+), 290 deletions(-) create mode 100644 TLM/TLM/State/Keybinds/KeybindSetting.cs rename TLM/TLM/State/Keybinds/{KeymappingSettings.cs => KeybindSettingsBase.cs} (50%) create mode 100644 TLM/TLM/State/Keybinds/KeybindSettingsPage.cs delete mode 100644 TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs delete mode 100644 TLM/TLM/State/Keybinds/TmpeRebindableKey.cs diff --git a/TLM/TLM/State/Keybinds/Keybind.cs b/TLM/TLM/State/Keybinds/Keybind.cs index 3c5c16d6c..0623d0dd4 100644 --- a/TLM/TLM/State/Keybinds/Keybind.cs +++ b/TLM/TLM/State/Keybinds/Keybind.cs @@ -1,4 +1,5 @@ using ColossalFramework; +using ColossalFramework.UI; using UnityEngine; namespace TrafficManager.State.Keybinds { @@ -20,5 +21,59 @@ public static bool IsEmpty(InputKey sample) { public static string Str(SavedInputKey k) { return k.ToLocalizedString("KEYNAME"); } + + public static bool IsModifierKey(KeyCode code) { + return code == KeyCode.LeftControl || code == KeyCode.RightControl || + code == KeyCode.LeftShift || code == KeyCode.RightShift || + code == KeyCode.LeftAlt || code == KeyCode.RightAlt; + } + + public static bool IsControlDown() { + return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + } + + public static bool IsShiftDown() { + return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + } + + public static bool IsAltDown() { + return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); + } + + public static bool IsUnbindableMouseButton(UIMouseButton code) { + return code == UIMouseButton.Left || code == UIMouseButton.Right; + } + + public static KeyCode ButtonToKeycode(UIMouseButton button) { + if (button == UIMouseButton.Left) { + return KeyCode.Mouse0; + } + + if (button == UIMouseButton.Right) { + return KeyCode.Mouse1; + } + + if (button == UIMouseButton.Middle) { + return KeyCode.Mouse2; + } + + if (button == UIMouseButton.Special0) { + return KeyCode.Mouse3; + } + + if (button == UIMouseButton.Special1) { + return KeyCode.Mouse4; + } + + if (button == UIMouseButton.Special2) { + return KeyCode.Mouse5; + } + + if (button == UIMouseButton.Special3) { + return KeyCode.Mouse6; + } + + return KeyCode.None; + } } } \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindSetting.cs b/TLM/TLM/State/Keybinds/KeybindSetting.cs new file mode 100644 index 000000000..b846d6fb9 --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeybindSetting.cs @@ -0,0 +1,99 @@ +using JetBrains.Annotations; +using UnityEngine; + +namespace TrafficManager.State.Keybinds { + using ColossalFramework; + + /// + /// Contains one or two SavedInputKeys, and event handler when the key is changed. + /// + public class KeybindSetting { + /// + /// Used by the GUI to tell the button event handler which key is being edited + /// + public struct Editable { + public KeybindSetting Target; + public SavedInputKey TargetKey; + } + + /// + /// Groups input keys by categories, also helps to know the usage for conflict search. + /// + public string Category; + + /// + /// The key itself, bound to a config file value + /// + public SavedInputKey Key; + + [CanBeNull] + public SavedInputKey AlternateKey; + + public KeybindSetting(string cat, + string configFileKey, + InputKey? defaultKey1 = null) { + Category = cat; + Key = new SavedInputKey( + configFileKey, + KeybindSettingsBase.KEYBOARD_SHORTCUTS_FILENAME, + defaultKey1 ?? SavedInputKey.Empty, + true); + } + + public KeybindSetting(string cat, + string configFileKey, + InputKey? defaultKey1, + InputKey? defaultKey2) { + Category = cat; + Key = new SavedInputKey( + configFileKey, + KeybindSettingsBase.KEYBOARD_SHORTCUTS_FILENAME, + defaultKey1 ?? SavedInputKey.Empty, + true); + AlternateKey = new SavedInputKey( + configFileKey + "_Alternate", + KeybindSettingsBase.KEYBOARD_SHORTCUTS_FILENAME, + defaultKey2 ?? SavedInputKey.Empty, + true); + } + + /// + /// Produce a keybind tooltip text, or two if alternate key is set. Prefixed if not empty. + /// + /// Prefix will be added if any key is not empty + /// String tooltip with the key shortcut or two + public string Str(string prefix = "") { + var result = default(string); + if (!Keybind.IsEmpty(Key)) { + result += prefix + Keybind.Str(Key); + } + + if (AlternateKey == null || Keybind.IsEmpty(AlternateKey)) { + return result; + } + + if (result.IsNullOrWhiteSpace()) { + result += prefix; + } else { + result += " | "; + } + + return result + Keybind.Str(AlternateKey); + } + + public bool IsPressed(Event e) { + return Key.IsPressed(e) + || (AlternateKey != null && AlternateKey.IsPressed(e)); + } + + /// + /// Check whether main or alt key are the same as k + /// + /// Find key + /// We have the key + public bool HasKey(InputKey k) { + return Key.value == k + || (AlternateKey != null && AlternateKey.value == k); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeymappingSettings.cs b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs similarity index 50% rename from TLM/TLM/State/Keybinds/KeymappingSettings.cs rename to TLM/TLM/State/Keybinds/KeybindSettingsBase.cs index 75846b12d..3153e3e67 100644 --- a/TLM/TLM/State/Keybinds/KeymappingSettings.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs @@ -1,106 +1,83 @@ // Based on keymapping module from CS-MoveIt mod // Thanks to https://github.com/Quboid/CS-MoveIt - -using System; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using ColossalFramework; -using ColossalFramework.Globalization; -using ColossalFramework.UI; -using CSUtil.Commons; using TrafficManager.UI; -using UnityEngine; namespace TrafficManager.State.Keybinds { - public class KeymappingSettings : UICustomControl { + using System; + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + using ColossalFramework; + using ColossalFramework.Globalization; + using ColossalFramework.UI; + using CSUtil.Commons; + using JetBrains.Annotations; + using UnityEngine; + + public class KeybindSettingsBase : UICustomControl { + private static void OnMainMenuShortcutChanged(SavedInputKey savedinputkey) { + LoadingExtension.BaseUI.MainMenuButton.UpdateTooltip(); + Log.Info("Main menu shortcut changed"); + } + protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; - private const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; + public const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; - /// + /// /// This input key can not be changed and is not checked, instead it is display only + /// + protected static KeybindSetting ToolCancelViewOnly { get; } = new KeybindSetting( + "Global", + "keyExitSubtool", + SavedInputKey.Encode(KeyCode.Escape, false, false, false)); + + public static KeybindSetting ToggleMainMenu { get; } = new KeybindSetting( + "Global", + "keyToggleTMPEMainMenu", + SavedInputKey.Encode(KeyCode.Semicolon, false, true, false)); + + public static KeybindSetting ToggleTrafficLightTool { get; } = + new KeybindSetting("Global", "keyToggleTrafficLightTool"); + + public static KeybindSetting LaneArrowTool { get; } = + new KeybindSetting("Global", "keyLaneArrowTool"); + + public static KeybindSetting LaneConnectionsTool { get; } = + new KeybindSetting("Global", "keyLaneConnectionsTool"); + + public static KeybindSetting PrioritySignsTool { get; } = + new KeybindSetting("Global", "keyPrioritySignsTool"); + + public static KeybindSetting JunctionRestrictionsTool { get; } = + new KeybindSetting("Global", "keyJunctionRestrictionsTool"); + + public static KeybindSetting SpeedLimitsTool { get; } = + new KeybindSetting("Global", "keySpeedLimitsTool"); + + public static KeybindSetting LaneConnectorStayInLane { get; } = new KeybindSetting( + "LaneConnector", + "keyLaneConnectorStayInLane", + SavedInputKey.Encode(KeyCode.S, false, true, false)); + + public static KeybindSetting LaneConnectorDelete { get; } = new KeybindSetting( + "LaneConnector", + "keyLaneConnectorDelete", + SavedInputKey.Encode(KeyCode.Delete, false, false, false), + SavedInputKey.Encode(KeyCode.Backspace, false, false, false)); + + private KeybindSetting.Editable? currentlyEditedBinding_; + + /// + /// Counter to produce alternating UI row colors (dark and light). /// - [TmpeRebindableKey("Global")] - public static SavedInputKey KeyToolCancel_ViewOnly = - new SavedInputKey("keyExitSubtool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.Escape, false, false, false), - false); - - [TmpeRebindableKey("Global")] - public static SavedInputKey KeyToggleTMPEMainMenu = - new SavedInputKey("keyToggleTMPEMainMenu", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.Semicolon, false, true, false), - true); - - [TmpeRebindableKey("Global")] - public static SavedInputKey KeyToggleTrafficLightTool = - new SavedInputKey("keyToggleTrafficLightTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TmpeRebindableKey("Global")] - public static SavedInputKey KeyLaneArrowTool = - new SavedInputKey("keyLaneArrowTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TmpeRebindableKey("Global")] - public static SavedInputKey KeyLaneConnectionsTool = - new SavedInputKey("keyLaneConnectionsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TmpeRebindableKey("Global")] - public static SavedInputKey KeyPrioritySignsTool = - new SavedInputKey("keyPrioritySignsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TmpeRebindableKey("Global")] - public static SavedInputKey KeyJunctionRestrictionsTool = - new SavedInputKey("keyJunctionRestrictionsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TmpeRebindableKey("Global")] - public static SavedInputKey KeySpeedLimitsTool = - new SavedInputKey("keySpeedLimitsTool", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Empty, - true); - - [TmpeRebindableKey("LaneConnector")] - public static SavedInputKey KeyLaneConnectorStayInLane = - new SavedInputKey("keyLaneConnectorStayInLane", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.S, false, true, false), - true); - - [TmpeRebindableKey("LaneConnector")] - public static SavedInputKey KeyLaneConnectorDelete = - new SavedInputKey("keyLaneConnectorDelete", - KEYBOARD_SHORTCUTS_FILENAME, - SavedInputKey.Encode(KeyCode.Delete, false, false, false), - true); - - private SavedInputKey editingBinding_; - private string editingBindingCategory_; - - private int count_; - - protected void TryCreateConfig() { + private int uiRowCount_; + + protected static void TryCreateConfig() { try { // Creating setting file if (GameSettings.FindSettingsFileByName(KEYBOARD_SHORTCUTS_FILENAME) == null) { - GameSettings.AddSettingsFile(new SettingsFile - {fileName = KEYBOARD_SHORTCUTS_FILENAME}); + GameSettings.AddSettingsFile( + new SettingsFile {fileName = KEYBOARD_SHORTCUTS_FILENAME}); } } catch (Exception) { @@ -109,14 +86,15 @@ protected void TryCreateConfig() { } /// - /// Creates a row in the current panel with the label and the button which will prompt user to press a new key. + /// Creates a row in the current panel with the label and the button + /// which will prompt user to press a new key. /// - /// Text to display - /// A SavedInputKey from GlobalConfig.KeyboardShortcuts - protected void AddKeymapping(string label, SavedInputKey savedInputKey, string category) { + /// Localized label + /// The setting to edit + protected void AddUiControl(string label, KeybindSetting keybind) { var uiPanel = component.AttachUIComponent( UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; - if (count_++ % 2 == 1) { + if (uiRowCount_++ % 2 == 1) { uiPanel.backgroundSprite = null; } @@ -127,9 +105,14 @@ protected void AddKeymapping(string label, SavedInputKey savedInputKey, string c var uiButton = uiPanel.Find("Binding"); uiButton.eventKeyDown += OnBindingKeyDown; uiButton.eventMouseDown += OnBindingMouseDown; - uiButton.text = Keybind.Str(savedInputKey); - uiButton.objectUserData = savedInputKey; - uiButton.stringUserData = category; + uiButton.text = Keybind.Str(keybind.Key); // take the first key only + + // Tell the button handler that we're editing the main Key of this keybind + uiButton.objectUserData + = new KeybindSetting.Editable { + Target = keybind, + TargetKey = keybind.Key + }; // Set label text (as provided) and set button text from the SavedInputKey uiLabel.text = label; @@ -139,12 +122,12 @@ protected void AddKeymapping(string label, SavedInputKey savedInputKey, string c /// Creates a line of key mapping but does not allow changing it. /// Used to improve awareness. /// - /// - /// - protected void AddReadOnlyKeymapping(string label, SavedInputKey inputKey) { + /// Localized label + /// The setting to edit + protected void AddReadOnlyUi(string label, KeybindSetting keybind) { var uiPanel = component.AttachUIComponent( UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; - if (count_++ % 2 == 1) { + if (uiRowCount_++ % 2 == 1) { uiPanel.backgroundSprite = null; } @@ -157,8 +140,7 @@ protected void AddReadOnlyKeymapping(string label, SavedInputKey inputKey) { // Set label text (as provided) and set button text from the InputKey uiLabel.text = label; - uiReadOnlyKey.text = Keybind.Str(inputKey); - uiReadOnlyKey.objectUserData = inputKey; + uiReadOnlyKey.text = keybind.Str(); } protected void OnEnable() { @@ -169,102 +151,56 @@ protected void OnDisable() { LocaleManager.eventLocaleChanged -= OnLocaleChanged; } - protected void OnLocaleChanged() { + private void OnLocaleChanged() { RefreshBindableInputs(); } - protected bool IsModifierKey(KeyCode code) { - return code == KeyCode.LeftControl || code == KeyCode.RightControl || - code == KeyCode.LeftShift || code == KeyCode.RightShift || code == KeyCode.LeftAlt || - code == KeyCode.RightAlt; - } - - protected bool IsControlDown() { - return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); - } - - protected bool IsShiftDown() { - return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - } - - protected bool IsAltDown() { - return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); - } - - protected bool IsUnbindableMouseButton(UIMouseButton code) { - return code == UIMouseButton.Left || code == UIMouseButton.Right; - } - - protected KeyCode ButtonToKeycode(UIMouseButton button) { - if (button == UIMouseButton.Left) { - return KeyCode.Mouse0; - } - - if (button == UIMouseButton.Right) { - return KeyCode.Mouse1; - } - - if (button == UIMouseButton.Middle) { - return KeyCode.Mouse2; + private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { + // This will only work if the user clicked the modify button + // otherwise no effect + if (currentlyEditedBinding_ == null || Keybind.IsModifierKey(p.keycode)) { + return; } - if (button == UIMouseButton.Special0) { - return KeyCode.Mouse3; - } + p.Use(); // Consume the event + UIView.PopModal(); + var keycode = p.keycode; + var inputKey = (p.keycode == KeyCode.Escape) + ? currentlyEditedBinding_.Value.TargetKey + : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); - if (button == UIMouseButton.Special1) { - return KeyCode.Mouse4; - } + var editable = (KeybindSetting.Editable)p.source.objectUserData; + var category = editable.Target.Category; - if (button == UIMouseButton.Special2) { - return KeyCode.Mouse5; + if (p.keycode == KeyCode.Backspace) { + // TODO: Show hint somewhere for Bksp and Esc special handling + inputKey = SavedInputKey.Empty; } - if (button == UIMouseButton.Special3) { - return KeyCode.Mouse6; + var maybeConflict = FindConflict(inputKey, category); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, + false); + } else { + currentlyEditedBinding_.Value.TargetKey.value = inputKey; } - return KeyCode.None; - } - - private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { - // This will only work if the user clicked the modify button - // otherwise no effect - if (editingBinding_ != null && !IsModifierKey(p.keycode)) { - p.Use(); - UIView.PopModal(); - var keycode = p.keycode; - var inputKey = (p.keycode == KeyCode.Escape) - ? editingBinding_.value - : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); - var category = p.source.stringUserData; - - if (p.keycode == KeyCode.Backspace) { - inputKey = SavedInputKey.Empty; - } - var maybeConflict = FindConflict(inputKey, category); - if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); - } else { - editingBinding_.value = inputKey; - } - - var uITextComponent = p.source as UITextComponent; - uITextComponent.text = Keybind.Str(editingBinding_); - editingBinding_ = null; - editingBindingCategory_ = string.Empty; - } + // Update text on the button + var uITextComponent = p.source as UITextComponent; + uITextComponent.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + currentlyEditedBinding_ = null; } private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { + var editable = (KeybindSetting.Editable)p.source.objectUserData; + // This will only work if the user is not in the process of changing the shortcut - if (editingBinding_ == null) { + if (currentlyEditedBinding_ == null) { p.Use(); - editingBinding_ = (SavedInputKey) p.source.objectUserData; - editingBindingCategory_ = p.source.stringUserData; + currentlyEditedBinding_ = editable; + var uIButton = p.source as UIButton; uIButton.buttonsMask = UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | @@ -273,14 +209,15 @@ private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { uIButton.text = "Press any key"; p.source.Focus(); UIView.PushModal(p.source); - } else if (!IsUnbindableMouseButton(p.buttons)) { + } else if (!Keybind.IsUnbindableMouseButton(p.buttons)) { // This will work if the user clicks while the shortcut change is in progress p.Use(); UIView.PopModal(); - var inputKey = SavedInputKey.Encode(ButtonToKeycode(p.buttons), - IsControlDown(), IsShiftDown(), - IsAltDown()); - var category = p.source.stringUserData; + var inputKey = SavedInputKey.Encode(Keybind.ButtonToKeycode(p.buttons), + Keybind.IsControlDown(), + Keybind.IsShiftDown(), + Keybind.IsAltDown()); + var category = editable.Target.Category; var maybeConflict = FindConflict(inputKey, category); if (maybeConflict != string.Empty) { UIView.library.ShowModal("ExceptionPanel").SetMessage( @@ -288,14 +225,13 @@ private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, false); } else { - editingBinding_.value = inputKey; + currentlyEditedBinding_.Value.TargetKey.value = inputKey; } var uIButton2 = p.source as UIButton; - uIButton2.text = Keybind.Str(editingBinding_); + uIButton2.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); uIButton2.buttonsMask = UIMouseButton.Left; - editingBinding_ = null; - editingBindingCategory_ = string.Empty; + currentlyEditedBinding_ = null; } } @@ -321,7 +257,8 @@ private void RefreshBindableInputs() { /// This covers game Settings class, and self (OptionsKeymapping class). /// /// Key to search for the conflicts - /// + /// Check the same category keys if possible + /// Empty string for no conflict, or the conflicting key name private string FindConflict(InputKey sample, string sampleCategory) { if (Keybind.IsEmpty(sample)) { // empty key never conflicts @@ -334,15 +271,15 @@ private string FindConflict(InputKey sample, string sampleCategory) { } // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. - var saveEditingBinding = editingBinding_; - editingBinding_.value = SavedInputKey.Empty; + var saveEditingBinding = currentlyEditedBinding_.Value.TargetKey.value; + currentlyEditedBinding_.Value.TargetKey.value = SavedInputKey.Empty; // Check in TMPE settings - var tmpeSettingsType = typeof(KeymappingSettings); + var tmpeSettingsType = typeof(KeybindSettingsBase); var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); - editingBinding_ = saveEditingBinding; + currentlyEditedBinding_.Value.TargetKey.value = saveEditingBinding; return inTmpe; } @@ -378,10 +315,12 @@ private static InputKey GetDefaultEntryInGameSettings(string entryName) { if (field == null) { return 0; } + var obj = field.GetValue(null); if (obj is InputKey) { return (InputKey)obj; } + return 0; } @@ -397,28 +336,22 @@ private static InputKey GetDefaultEntryInGameSettings(string entryName) { private static string FindConflictInTmpe(InputKey sample, string sampleCategory, FieldInfo[] fields) { foreach (var field in fields) { // This will match inputkeys of TMPE key settings - if (field.FieldType != typeof(SavedInputKey)) { + if (field.FieldType != typeof(KeybindSetting)) { continue; } - var rebindableKeyAttrs = field.GetCustomAttributes( - typeof(TmpeRebindableKey), - false) as TmpeRebindableKey[]; - if (rebindableKeyAttrs == null || rebindableKeyAttrs.Length <= 0) { - continue; - } + var tmpeSetting = field.GetValue(null) as KeybindSetting; // Check category, category=Global will check keys in all categories // category= will check Global and its own only - var rebindableKeyCategory = rebindableKeyAttrs[0].Category; - if (sampleCategory != "Global" && sampleCategory != rebindableKeyCategory) { + if (sampleCategory != "Global" + && sampleCategory != tmpeSetting.Category) { continue; } - var key = (SavedInputKey) field.GetValue(null); - if (key.value == sample) { + if (tmpeSetting.HasKey(sample)) { return "TM:PE, " - + Translation.GetString("Keybind_category_" + rebindableKeyCategory) + + Translation.GetString("Keybind_category_" + tmpeSetting.Category) + " -- " + CamelCaseSplit(field.Name); } } diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs new file mode 100644 index 000000000..104d7cf54 --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -0,0 +1,35 @@ +using TrafficManager.UI; + +namespace TrafficManager.State.Keybinds { + public class KeybindSettingsPage : KeybindSettingsBase { + private void Awake() { + TryCreateConfig(); + + AddReadOnlyUi(Translation.GetString("Keybind_Exit_subtool"), + ToolCancelViewOnly); + + AddUiControl(Translation.GetString("Keybind_toggle_TMPE_main_menu"), + ToggleMainMenu); + + AddUiControl(Translation.GetString("Keybind_toggle_traffic_lights_tool"), + ToggleTrafficLightTool); + AddUiControl(Translation.GetString("Keybind_use_lane_arrow_tool"), + LaneArrowTool); + AddUiControl(Translation.GetString("Keybind_use_lane_connections_tool"), + LaneConnectionsTool); + AddUiControl(Translation.GetString("Keybind_use_priority_signs_tool"), + PrioritySignsTool); + AddUiControl(Translation.GetString("Keybind_use_junction_restrictions_tool"), + JunctionRestrictionsTool); + AddUiControl(Translation.GetString("Keybind_use_speed_limits_tool"), + SpeedLimitsTool); + + // New section: Lane Connector Tool + AddUiControl(Translation.GetString("Keybind_lane_connector_stay_in_lane"), + LaneConnectorStayInLane); + + AddUiControl(Translation.GetString("Keybind_lane_connector_delete"), + LaneConnectorDelete); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs b/TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs deleted file mode 100644 index 80764b533..000000000 --- a/TLM/TLM/State/Keybinds/KeymappingSettingsMain.cs +++ /dev/null @@ -1,35 +0,0 @@ -using TrafficManager.UI; - -namespace TrafficManager.State.Keybinds { - public class KeymappingSettingsMain : KeymappingSettings { - private void Awake() { - TryCreateConfig(); - - AddReadOnlyKeymapping(Translation.GetString("Keybind_Exit_subtool"), - KeyToolCancel_ViewOnly); - - AddKeymapping(Translation.GetString("Keybind_toggle_TMPE_main_menu"), - KeyToggleTMPEMainMenu, "Global"); - - AddKeymapping(Translation.GetString("Keybind_toggle_traffic_lights_tool"), - KeyToggleTrafficLightTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_lane_arrow_tool"), - KeyLaneArrowTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_lane_connections_tool"), - KeyLaneConnectionsTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_priority_signs_tool"), - KeyPrioritySignsTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_junction_restrictions_tool"), - KeyJunctionRestrictionsTool, "Global"); - AddKeymapping(Translation.GetString("Keybind_use_speed_limits_tool"), - KeySpeedLimitsTool, "Global"); - - // New section: Lane Connector Tool - AddKeymapping(Translation.GetString("Keybind_lane_connector_stay_in_lane"), - KeyLaneConnectorStayInLane, "LaneConnector"); - - AddKeymapping(Translation.GetString("Keybind_lane_connector_delete"), - KeyLaneConnectorDelete, "LaneConnector"); - } - } -} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/TmpeRebindableKey.cs b/TLM/TLM/State/Keybinds/TmpeRebindableKey.cs deleted file mode 100644 index 54ab42dab..000000000 --- a/TLM/TLM/State/Keybinds/TmpeRebindableKey.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace TrafficManager.State.Keybinds { - /// - /// This attribute is used on key bindings and tells us where this key is used, - /// to allow using the same key in multiple occasions we need to know the category. - /// - [AttributeUsage(AttributeTargets.Field)] - public class TmpeRebindableKey: Attribute { - public string Category; - public TmpeRebindableKey(string cat) { - Category = cat; - } - } -} \ No newline at end of file diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index 82ba59e1e..66da464a4 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -233,7 +233,7 @@ private static void MakeSettings_Keybinds(UITabstrip tabStrip, int tabIndex) { panelHelper = new UIHelper(currentPanel); var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); - ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); + ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); } private static void MakeSettings_Maintenance(UITabstrip tabStrip, int tabIndex) { diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 2c5836dbf..8ae7579ad 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -177,9 +177,9 @@ - - - + + + diff --git a/TLM/TLM/UI/LinearSpriteButton.cs b/TLM/TLM/UI/LinearSpriteButton.cs index 268022705..b771d4fb7 100644 --- a/TLM/TLM/UI/LinearSpriteButton.cs +++ b/TLM/TLM/UI/LinearSpriteButton.cs @@ -101,7 +101,7 @@ public override void Start() { public abstract void HandleClick(UIMouseEventParameter p); - public virtual SavedInputKey ShortcutKey { + public virtual KeybindSetting ShortcutKey { get { return null; } } @@ -147,10 +147,7 @@ internal void UpdateProperties() { ///
/// Tooltip to append to the main tooltip text, or an empty string private string GetShortcutTooltip() { - var isNotSet = ShortcutKey == null || Keybind.IsEmpty(ShortcutKey.value); - return isNotSet - ? string.Empty - : "\n" + Keybind.Str(ShortcutKey); + return ShortcutKey.Str("\n"); } } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs index 19a67cb43..f721e8baf 100644 --- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs @@ -8,6 +8,6 @@ public class JunctionRestrictionsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; public override string Tooltip => "Junction_restrictions"; public override bool Visible => Options.junctionRestrictionsEnabled; - public override SavedInputKey ShortcutKey => KeymappingSettings.KeyJunctionRestrictionsTool; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.JunctionRestrictionsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs index 41e2a7ddb..a997b8e8f 100644 --- a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs @@ -8,6 +8,6 @@ public class LaneArrowsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.LaneArrows; public override string Tooltip => "Change_lane_arrows"; public override bool Visible => true; - public override SavedInputKey ShortcutKey => KeymappingSettings.KeyLaneArrowTool; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.LaneArrowTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs index bb32f5169..e9315cccb 100644 --- a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs @@ -8,6 +8,6 @@ public class LaneConnectorButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.LaneConnector; public override string Tooltip => "Lane_connector"; public override bool Visible => Options.laneConnectorEnabled; - public override SavedInputKey ShortcutKey => KeymappingSettings.KeyLaneConnectionsTool; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.LaneConnectionsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index 131247609..5c701a9f8 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -219,17 +219,17 @@ public void OnGUI() { // Some safety checks to not trigger while full screen/modals are open // Check the key and then click the corresponding button - if (KeymappingSettings.KeyToggleTrafficLightTool.IsPressed(Event.current)) { + if (KeybindSettingsBase.ToggleTrafficLightTool.IsPressed(Event.current)) { ClickToolButton(typeof(ToggleTrafficLightsButton)); - } else if (KeymappingSettings.KeyLaneArrowTool.IsPressed(Event.current)) { + } else if (KeybindSettingsBase.LaneArrowTool.IsPressed(Event.current)) { ClickToolButton(typeof(LaneArrowsButton)); - } else if (KeymappingSettings.KeyLaneConnectionsTool.IsPressed(Event.current)) { + } else if (KeybindSettingsBase.LaneConnectionsTool.IsPressed(Event.current)) { ClickToolButton(typeof(LaneConnectorButton)); - } else if (KeymappingSettings.KeyPrioritySignsTool.IsPressed(Event.current)) { + } else if (KeybindSettingsBase.PrioritySignsTool.IsPressed(Event.current)) { ClickToolButton(typeof(PrioritySignsButton)); - } else if (KeymappingSettings.KeyJunctionRestrictionsTool.IsPressed(Event.current)) { + } else if (KeybindSettingsBase.JunctionRestrictionsTool.IsPressed(Event.current)) { ClickToolButton(typeof(JunctionRestrictionsButton)); - } else if (KeymappingSettings.KeySpeedLimitsTool.IsPressed(Event.current)) { + } else if (KeybindSettingsBase.SpeedLimitsTool.IsPressed(Event.current)) { ClickToolButton(typeof(SpeedLimitsButton)); } } diff --git a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs index 7a49e7f3b..8003c9e99 100644 --- a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs +++ b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs @@ -8,6 +8,6 @@ public class PrioritySignsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.PrioritySigns; public override string Tooltip => "Add_priority_signs"; public override bool Visible => Options.prioritySignsEnabled; - public override SavedInputKey ShortcutKey => KeymappingSettings.KeyPrioritySignsTool; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.PrioritySignsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs index dc9c34683..eeb0ae28a 100644 --- a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs +++ b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs @@ -8,6 +8,6 @@ public class SpeedLimitsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.SpeedLimits; public override string Tooltip => "Speed_limits"; public override bool Visible => Options.customSpeedLimitsEnabled; - public override SavedInputKey ShortcutKey => KeymappingSettings.KeySpeedLimitsTool; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.SpeedLimitsTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs index 8c9e71e3f..b59297aff 100644 --- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs @@ -8,6 +8,6 @@ public class ToggleTrafficLightsButton : MenuToolModeButton { public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; public override string Tooltip => "Switch_traffic_lights"; public override bool Visible => true; - public override SavedInputKey ShortcutKey => KeymappingSettings.KeyToggleTrafficLightTool; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.ToggleTrafficLightTool; } } \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index 9c360cc87..24d1b9a00 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -65,13 +65,13 @@ public override void OnToolGUI(Event e) { // $"SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} " + // $"HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); - if (KeymappingSettings.KeyLaneConnectorStayInLane.IsPressed(e)) { + if (KeybindSettingsBase.LaneConnectorStayInLane.IsPressed(e)) { frameStayInLanePressed = Time.frameCount; // this will be consumed in RenderOverlay() if the key was pressed // not too long ago (within 20 Unity frames or 0.33 sec) } - if (KeymappingSettings.KeyLaneConnectorDelete.IsPressed(e)) { + if (KeybindSettingsBase.LaneConnectorDelete.IsPressed(e)) { frameClearPressed = Time.frameCount; // this will be consumed in RenderOverlay() if the key was pressed // not too long ago (within 20 Unity frames or 0.33 sec) diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 37b5e8c46..352cb8f2d 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -67,6 +67,13 @@ public override void Start() { m_TooltipBox = uiView.defaultTooltipBox; } + UpdateTooltip(); + } + + /// + /// Reset the tooltip (or set for the first time), or if keybinding has changed + /// + public void UpdateTooltip() { tooltip = Translation.GetString("Keybind_toggle_TMPE_main_menu") + GetTooltip(); } @@ -139,7 +146,7 @@ public void UpdatePosition(Vector2 pos) { public void OnGUI() { if (!UIView.HasModalInput() && !UIView.HasInputFocus() - && KeymappingSettings.KeyToggleTMPEMainMenu.IsPressed(Event.current)) { + && KeybindSettingsBase.ToggleMainMenu.IsPressed(Event.current)) { if (LoadingExtension.BaseUI != null) { LoadingExtension.BaseUI.ToggleMainMenu(); } @@ -147,11 +154,7 @@ public void OnGUI() { } private string GetTooltip() { - if (Keybind.IsEmpty(KeymappingSettings.KeyToggleTMPEMainMenu)) { - return string.Empty; - } - - return "\n" + Keybind.Str(KeymappingSettings.KeyToggleTMPEMainMenu); + return KeybindSettingsBase.ToggleMainMenu.Str("\n"); } } } \ No newline at end of file From d1d6e47dc52f76c6b0d61aa0fd67a54358080d70 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 30 Jun 2019 14:39:00 +0200 Subject: [PATCH 104/142] Refactored GUI for Keybinds settings; X button to clear --- TLM/TLM/State/Keybinds/KeybindSettingsBase.cs | 324 ++++-------------- TLM/TLM/State/Keybinds/KeybindSettingsPage.cs | 42 +-- TLM/TLM/State/Keybinds/KeybindUI.cs | 297 ++++++++++++++++ TLM/TLM/TLM.csproj | 1 + .../UI/MainMenu/JunctionRestrictionsButton.cs | 3 +- TLM/TLM/UI/MainMenu/LaneArrowsButton.cs | 4 +- .../UI/MainMenu/ToggleTrafficLightsButton.cs | 4 +- 7 files changed, 383 insertions(+), 292 deletions(-) create mode 100644 TLM/TLM/State/Keybinds/KeybindUI.cs diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs index 3153e3e67..4948ee57f 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs @@ -1,17 +1,11 @@ // Based on keymapping module from CS-MoveIt mod // Thanks to https://github.com/Quboid/CS-MoveIt -using TrafficManager.UI; - namespace TrafficManager.State.Keybinds { using System; - using System.Linq; - using System.Reflection; - using System.Text.RegularExpressions; using ColossalFramework; using ColossalFramework.Globalization; using ColossalFramework.UI; using CSUtil.Commons; - using JetBrains.Annotations; using UnityEngine; public class KeybindSettingsBase : UICustomControl { @@ -21,51 +15,51 @@ private static void OnMainMenuShortcutChanged(SavedInputKey savedinputkey) { } protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; - public const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keyboard"; + public const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keybinds"; /// /// This input key can not be changed and is not checked, instead it is display only /// protected static KeybindSetting ToolCancelViewOnly { get; } = new KeybindSetting( "Global", - "keyExitSubtool", + "Key_ExitSubtool", SavedInputKey.Encode(KeyCode.Escape, false, false, false)); public static KeybindSetting ToggleMainMenu { get; } = new KeybindSetting( "Global", - "keyToggleTMPEMainMenu", + "Key_ToggleTMPEMainMenu", SavedInputKey.Encode(KeyCode.Semicolon, false, true, false)); public static KeybindSetting ToggleTrafficLightTool { get; } = - new KeybindSetting("Global", "keyToggleTrafficLightTool"); + new KeybindSetting("Global", "Key_ToggleTrafficLightTool"); public static KeybindSetting LaneArrowTool { get; } = - new KeybindSetting("Global", "keyLaneArrowTool"); + new KeybindSetting("Global", "Key_LaneArrowTool"); public static KeybindSetting LaneConnectionsTool { get; } = - new KeybindSetting("Global", "keyLaneConnectionsTool"); + new KeybindSetting("Global", "Key_LaneConnectionsTool"); public static KeybindSetting PrioritySignsTool { get; } = - new KeybindSetting("Global", "keyPrioritySignsTool"); + new KeybindSetting("Global", "Key_PrioritySignsTool"); public static KeybindSetting JunctionRestrictionsTool { get; } = - new KeybindSetting("Global", "keyJunctionRestrictionsTool"); + new KeybindSetting("Global", "Key_JunctionRestrictionsTool"); public static KeybindSetting SpeedLimitsTool { get; } = - new KeybindSetting("Global", "keySpeedLimitsTool"); + new KeybindSetting("Global", "Key_SpeedLimitsTool"); public static KeybindSetting LaneConnectorStayInLane { get; } = new KeybindSetting( "LaneConnector", - "keyLaneConnectorStayInLane", + "Key_LaneConnector_StayInLane", SavedInputKey.Encode(KeyCode.S, false, true, false)); public static KeybindSetting LaneConnectorDelete { get; } = new KeybindSetting( "LaneConnector", - "keyLaneConnectorDelete", + "Key_LaneConnector_Delete", SavedInputKey.Encode(KeyCode.Delete, false, false, false), SavedInputKey.Encode(KeyCode.Backspace, false, false, false)); - private KeybindSetting.Editable? currentlyEditedBinding_; + private KeybindUI keybindUi_ = new KeybindUI(); /// /// Counter to produce alternating UI row colors (dark and light). @@ -85,37 +79,40 @@ protected static void TryCreateConfig() { } } + protected void BeginForm() { + keybindUi_.BeginForm(component); + } + /// /// Creates a row in the current panel with the label and the button /// which will prompt user to press a new key. /// /// Localized label /// The setting to edit - protected void AddUiControl(string label, KeybindSetting keybind) { - var uiPanel = component.AttachUIComponent( - UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; + protected void AddKeybindRowUI(string label, KeybindSetting keybind) { + var settingsRow = keybindUi_.CreateRowPanel(); if (uiRowCount_++ % 2 == 1) { - uiPanel.backgroundSprite = null; + settingsRow.backgroundSprite = null; } - // Create a label - var uiLabel = uiPanel.Find("Name"); - - // Create a button which displays the shortcut and modifies it on click - var uiButton = uiPanel.Find("Binding"); - uiButton.eventKeyDown += OnBindingKeyDown; - uiButton.eventMouseDown += OnBindingMouseDown; - uiButton.text = Keybind.Str(keybind.Key); // take the first key only + keybindUi_.CreateLabel(settingsRow, label); + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key); + } - // Tell the button handler that we're editing the main Key of this keybind - uiButton.objectUserData - = new KeybindSetting.Editable { - Target = keybind, - TargetKey = keybind.Key - }; + /// + /// Add a second key under the first key, using same row background as the + /// previous key editor. + /// + /// + protected void AddAlternateUiControl(KeybindSetting keybind) { + var settingsRow = keybindUi_.CreateRowPanel(); + if (uiRowCount_ % 2 == 1) { + // color the panel but do not increment uiRowCount + settingsRow.backgroundSprite = null; + } - // Set label text (as provided) and set button text from the SavedInputKey - uiLabel.text = label; + keybindUi_.CreateLabel(settingsRow, string.Empty); + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.AlternateKey); } /// @@ -124,23 +121,14 @@ protected void AddUiControl(string label, KeybindSetting keybind) { /// /// Localized label /// The setting to edit - protected void AddReadOnlyUi(string label, KeybindSetting keybind) { - var uiPanel = component.AttachUIComponent( - UITemplateManager.GetAsGameObject(KeyBindingTemplate)) as UIPanel; + protected void ReadOnlyKeybindUI(string label, KeybindSetting keybind) { + var settingsRow = keybindUi_.CreateRowPanel(); if (uiRowCount_++ % 2 == 1) { - uiPanel.backgroundSprite = null; + settingsRow.backgroundSprite = null; } - // Create a label - var uiLabel = uiPanel.Find("Name"); - - // Create a button which displays the shortcut and modifies it on click - var uiReadOnlyKey = uiPanel.Find("Binding"); - uiReadOnlyKey.Disable(); - - // Set label text (as provided) and set button text from the InputKey - uiLabel.text = label; - uiReadOnlyKey.text = keybind.Str(); + keybindUi_.CreateLabel(settingsRow, label); + keybindUi_.CreateKeybindText(settingsRow, keybind.Key); } protected void OnEnable() { @@ -152,219 +140,27 @@ protected void OnDisable() { } private void OnLocaleChanged() { - RefreshBindableInputs(); - } - - private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { - // This will only work if the user clicked the modify button - // otherwise no effect - if (currentlyEditedBinding_ == null || Keybind.IsModifierKey(p.keycode)) { - return; - } - - p.Use(); // Consume the event - UIView.PopModal(); - var keycode = p.keycode; - var inputKey = (p.keycode == KeyCode.Escape) - ? currentlyEditedBinding_.Value.TargetKey - : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); - - var editable = (KeybindSetting.Editable)p.source.objectUserData; - var category = editable.Target.Category; - - if (p.keycode == KeyCode.Backspace) { - // TODO: Show hint somewhere for Bksp and Esc special handling - inputKey = SavedInputKey.Empty; - } - - var maybeConflict = FindConflict(inputKey, category); - if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); - } else { - currentlyEditedBinding_.Value.TargetKey.value = inputKey; - } - - // Update text on the button - var uITextComponent = p.source as UITextComponent; - uITextComponent.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); - currentlyEditedBinding_ = null; - } - - private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { - var editable = (KeybindSetting.Editable)p.source.objectUserData; - - // This will only work if the user is not in the process of changing the shortcut - if (currentlyEditedBinding_ == null) { - p.Use(); - currentlyEditedBinding_ = editable; - - var uIButton = p.source as UIButton; - uIButton.buttonsMask = - UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | - UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | - UIMouseButton.Special3; - uIButton.text = "Press any key"; - p.source.Focus(); - UIView.PushModal(p.source); - } else if (!Keybind.IsUnbindableMouseButton(p.buttons)) { - // This will work if the user clicks while the shortcut change is in progress - p.Use(); - UIView.PopModal(); - var inputKey = SavedInputKey.Encode(Keybind.ButtonToKeycode(p.buttons), - Keybind.IsControlDown(), - Keybind.IsShiftDown(), - Keybind.IsAltDown()); - var category = editable.Target.Category; - var maybeConflict = FindConflict(inputKey, category); - if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); - } else { - currentlyEditedBinding_.Value.TargetKey.value = inputKey; - } - - var uIButton2 = p.source as UIButton; - uIButton2.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); - uIButton2.buttonsMask = UIMouseButton.Left; - currentlyEditedBinding_ = null; - } - } - - private void RefreshBindableInputs() { - foreach (var current in component.GetComponentsInChildren()) { - var uITextComponent = current.Find("Binding"); - if (uITextComponent != null) { - var savedInputKey = uITextComponent.objectUserData as SavedInputKey; - if (savedInputKey != null) { - uITextComponent.text = Keybind.Str(savedInputKey); - } - } - - var uILabel = current.Find("Name"); - if (uILabel != null) { - uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); - } - } - } - - /// - /// For an inputkey, try find where possibly it is already used. - /// This covers game Settings class, and self (OptionsKeymapping class). - /// - /// Key to search for the conflicts - /// Check the same category keys if possible - /// Empty string for no conflict, or the conflicting key name - private string FindConflict(InputKey sample, string sampleCategory) { - if (Keybind.IsEmpty(sample)) { - // empty key never conflicts - return string.Empty; - } - - var inGameSettings = FindConflictInGameSettings(sample); - if (!string.IsNullOrEmpty(inGameSettings)) { - return inGameSettings; - } - - // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. - var saveEditingBinding = currentlyEditedBinding_.Value.TargetKey.value; - currentlyEditedBinding_.Value.TargetKey.value = SavedInputKey.Empty; - - // Check in TMPE settings - var tmpeSettingsType = typeof(KeybindSettingsBase); - var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); - - var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); - currentlyEditedBinding_.Value.TargetKey.value = saveEditingBinding; - return inTmpe; - } - - private static string FindConflictInGameSettings(InputKey sample) { - var fieldList = typeof(Settings).GetFields(BindingFlags.Static | BindingFlags.Public); - foreach (var field in fieldList) { - var customAttributes = field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; - if (customAttributes != null && customAttributes.Length > 0) { - var category = customAttributes[0].category; - if (category != string.Empty && category != "Game") { - // Ignore other categories: MapEditor, Decoration, ThemeEditor, ScenarioEditor - continue; - } - - var str = field.GetValue(null) as string; - - var savedInputKey = new SavedInputKey(str, - Settings.gameSettingsFile, - GetDefaultEntryInGameSettings(str), - true); - if (savedInputKey.value == sample) { - return (category == string.Empty ? string.Empty : (category + " -- ")) - + CamelCaseSplit(field.Name); - } - } - } - - return string.Empty; + // RefreshBindableInputs(); } - private static InputKey GetDefaultEntryInGameSettings(string entryName) { - var field = typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); - if (field == null) { - return 0; - } - - var obj = field.GetValue(null); - if (obj is InputKey) { - return (InputKey)obj; - } - - return 0; - } - - /// - /// For given key and category check TM:PE settings for the Global category - /// and the same category if it is not Global. This will allow reusing key in other tool - /// categories without conflicting. - /// - /// The key to search for - /// The category Global or some tool name - /// Fields of the key settings class - /// Empty string if no conflicts otherwise the key name to print an error - private static string FindConflictInTmpe(InputKey sample, string sampleCategory, FieldInfo[] fields) { - foreach (var field in fields) { - // This will match inputkeys of TMPE key settings - if (field.FieldType != typeof(KeybindSetting)) { - continue; - } - - var tmpeSetting = field.GetValue(null) as KeybindSetting; - - // Check category, category=Global will check keys in all categories - // category= will check Global and its own only - if (sampleCategory != "Global" - && sampleCategory != tmpeSetting.Category) { - continue; - } - - if (tmpeSetting.HasKey(sample)) { - return "TM:PE, " - + Translation.GetString("Keybind_category_" + tmpeSetting.Category) - + " -- " + CamelCaseSplit(field.Name); - } - } - - return string.Empty; - } - - private static string CamelCaseSplit(string s) { - var words = Regex.Matches(s, @"([A-Z][a-z]+)") - .Cast() - .Select(m => m.Value); - - return string.Join(" ", words.ToArray()); - } +// /// +// /// Called on locale change, resets keys to the new language +// /// +// private void RefreshBindableInputs() { +// foreach (var current in component.GetComponentsInChildren()) { +// var uITextComponent = current.Find("Binding"); +// if (uITextComponent != null) { +// var savedInputKey = uITextComponent.objectUserData as SavedInputKey; +// if (savedInputKey != null) { +// uITextComponent.text = Keybind.Str(savedInputKey); +// } +// } +// +// var uILabel = current.Find("Name"); +// if (uILabel != null) { +// uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); +// } +// } +// } } } \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs index 104d7cf54..1b0126ffa 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -5,31 +5,33 @@ public class KeybindSettingsPage : KeybindSettingsBase { private void Awake() { TryCreateConfig(); - AddReadOnlyUi(Translation.GetString("Keybind_Exit_subtool"), - ToolCancelViewOnly); + BeginForm(); + ReadOnlyKeybindUI(Translation.GetString("Keybind_Exit_subtool"), + ToolCancelViewOnly); - AddUiControl(Translation.GetString("Keybind_toggle_TMPE_main_menu"), - ToggleMainMenu); + AddKeybindRowUI(Translation.GetString("Keybind_toggle_TMPE_main_menu"), + ToggleMainMenu); - AddUiControl(Translation.GetString("Keybind_toggle_traffic_lights_tool"), - ToggleTrafficLightTool); - AddUiControl(Translation.GetString("Keybind_use_lane_arrow_tool"), - LaneArrowTool); - AddUiControl(Translation.GetString("Keybind_use_lane_connections_tool"), - LaneConnectionsTool); - AddUiControl(Translation.GetString("Keybind_use_priority_signs_tool"), - PrioritySignsTool); - AddUiControl(Translation.GetString("Keybind_use_junction_restrictions_tool"), - JunctionRestrictionsTool); - AddUiControl(Translation.GetString("Keybind_use_speed_limits_tool"), - SpeedLimitsTool); + AddKeybindRowUI(Translation.GetString("Keybind_toggle_traffic_lights_tool"), + ToggleTrafficLightTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_lane_arrow_tool"), + LaneArrowTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_lane_connections_tool"), + LaneConnectionsTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_priority_signs_tool"), + PrioritySignsTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_junction_restrictions_tool"), + JunctionRestrictionsTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_speed_limits_tool"), + SpeedLimitsTool); // New section: Lane Connector Tool - AddUiControl(Translation.GetString("Keybind_lane_connector_stay_in_lane"), - LaneConnectorStayInLane); + AddKeybindRowUI(Translation.GetString("Keybind_lane_connector_stay_in_lane"), + LaneConnectorStayInLane); - AddUiControl(Translation.GetString("Keybind_lane_connector_delete"), - LaneConnectorDelete); + ReadOnlyKeybindUI(Translation.GetString("Keybind_lane_connector_delete"), + LaneConnectorDelete); + AddAlternateUiControl(LaneConnectorDelete); } } } \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs new file mode 100644 index 000000000..4d23bc8be --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -0,0 +1,297 @@ +namespace TrafficManager.State.Keybinds { + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + using ColossalFramework; + using ColossalFramework.UI; + using UI; + using UnityEngine; + + /// + /// Helper for creating keyboard bindings Settings page. + /// + public class KeybindUI { + private KeybindSetting.Editable? currentlyEditedBinding_; + private const float ROW_WIDTH = 744f; + private const float ROW_HEIGHT = 34f; + + /// + /// Scrollable panel for keybinds, first created on Unity Awake call + /// + private UIComponent settingsPanel_; + + public void BeginForm(UIComponent component) { + settingsPanel_ = CreateScrollablePanel(component); + } + + /// + /// Creates a row for keyboard bindings editor. The row will contain a text + /// label, a button to edit the key, and X button to delete the key. + /// + /// The component where the UI is attached + /// The new scrollable panel + public static UIComponent CreateScrollablePanel(UIComponent root) { + var scrollablePanel = root.AddUIComponent(); + scrollablePanel.backgroundSprite = string.Empty; + scrollablePanel.size = new Vector2(ROW_WIDTH, 0); + scrollablePanel.relativePosition = Vector3.zero; + scrollablePanel.clipChildren = true; + scrollablePanel.autoLayoutStart = LayoutStart.TopLeft; + scrollablePanel.autoLayoutDirection = LayoutDirection.Vertical; + scrollablePanel.autoLayout = true; + + return scrollablePanel; + } + + public UIPanel CreateRowPanel() { + settingsPanel_.size += new Vector2(0f, ROW_HEIGHT); + + var p = settingsPanel_.AddUIComponent(); + p.size = new Vector2(ROW_WIDTH, ROW_HEIGHT); + p.autoLayoutStart = LayoutStart.TopLeft; + p.autoLayoutDirection = LayoutDirection.Horizontal; + p.autoLayout = true; + + return p; + } + + public void CreateLabel(UIPanel parent, string text) { + var label = parent.AddUIComponent(); + label.autoSize = false; + label.size = new Vector2(ROW_WIDTH * 0.6f, ROW_HEIGHT); + label.text = text; + label.verticalAlignment = UIVerticalAlignment.Middle; + label.textAlignment = UIHorizontalAlignment.Left; + } + + public void CreateKeybindButton(UIPanel parent, KeybindSetting setting, SavedInputKey editKey) { + var btn = parent.AddUIComponent(); + btn.size = new Vector2(ROW_WIDTH * 0.3f, ROW_HEIGHT); + btn.text = Keybind.Str(editKey); + btn.hoveredTextColor = new Color32(128, 128, 255, 255); // darker blue + btn.pressedTextColor = new Color32(192, 192, 255, 255); // lighter blue + btn.normalBgSprite = "ButtonMenu"; + + btn.eventKeyDown += OnBindingKeyDown; + btn.eventMouseDown += OnBindingMouseDown; + btn.objectUserData + = new KeybindSetting.Editable { Target = setting, TargetKey = editKey }; + + // Add X button + var btnX = parent.AddUIComponent(); + btnX.autoSize = false; + btnX.size = new Vector2(ROW_HEIGHT, ROW_HEIGHT); + btnX.normalBgSprite = "ButtonMenu"; + btnX.text = "X"; + btnX.eventClicked += (component, eventParam) => { + editKey.value = SavedInputKey.Empty; + btn.text = Keybind.Str(editKey); + }; + } + + /// + /// Create read-only display of a key binding + /// + /// The panel to host it + /// The key to display + public void CreateKeybindText(UIPanel parent, SavedInputKey showKey) { + var label = parent.AddUIComponent(); + label.autoSize = false; + label.size = new Vector2(ROW_WIDTH * 0.3f, ROW_HEIGHT); + label.text = Keybind.Str(showKey); + label.verticalAlignment = UIVerticalAlignment.Middle; + label.textAlignment = UIHorizontalAlignment.Center; + label.textColor = new Color32(128, 128, 128, 255); // grey + } + + private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { + // This will only work if the user clicked the modify button + // otherwise no effect + if (currentlyEditedBinding_ == null || Keybind.IsModifierKey(p.keycode)) { + return; + } + + p.Use(); // Consume the event + UIView.PopModal(); + var keycode = p.keycode; + var inputKey = (p.keycode == KeyCode.Escape) + ? currentlyEditedBinding_.Value.TargetKey + : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); + + var editable = (KeybindSetting.Editable)p.source.objectUserData; + var category = editable.Target.Category; + + var maybeConflict = FindConflict(inputKey, category); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, + false); + } else { + currentlyEditedBinding_.Value.TargetKey.value = inputKey; + } + + // Update text on the button + var uITextComponent = p.source as UITextComponent; + uITextComponent.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + currentlyEditedBinding_ = null; + } + + private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { + var editable = (KeybindSetting.Editable)p.source.objectUserData; + + // This will only work if the user is not in the process of changing the shortcut + if (currentlyEditedBinding_ == null) { + p.Use(); + currentlyEditedBinding_ = editable; + + var uIButton = p.source as UIButton; + uIButton.buttonsMask = + UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | + UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | + UIMouseButton.Special3; + uIButton.text = "Press any key"; + p.source.Focus(); + UIView.PushModal(p.source); + } else if (!Keybind.IsUnbindableMouseButton(p.buttons)) { + // This will work if the user clicks while the shortcut change is in progress + p.Use(); + UIView.PopModal(); + var inputKey = SavedInputKey.Encode(Keybind.ButtonToKeycode(p.buttons), + Keybind.IsControlDown(), + Keybind.IsShiftDown(), + Keybind.IsAltDown()); + var category = editable.Target.Category; + var maybeConflict = FindConflict(inputKey, category); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, + false); + } else { + currentlyEditedBinding_.Value.TargetKey.value = inputKey; + } + + var uIButton2 = p.source as UIButton; + uIButton2.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + uIButton2.buttonsMask = UIMouseButton.Left; + currentlyEditedBinding_ = null; + } + } + + /// + /// For an inputkey, try find where possibly it is already used. + /// This covers game Settings class, and self (OptionsKeymapping class). + /// + /// Key to search for the conflicts + /// Check the same category keys if possible + /// Empty string for no conflict, or the conflicting key name + private string FindConflict(InputKey sample, string sampleCategory) { + if (Keybind.IsEmpty(sample)) { + // empty key never conflicts + return string.Empty; + } + + var inGameSettings = FindConflictInGameSettings(sample); + if (!string.IsNullOrEmpty(inGameSettings)) { + return inGameSettings; + } + + // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. + var saveEditingBinding = currentlyEditedBinding_.Value.TargetKey.value; + currentlyEditedBinding_.Value.TargetKey.value = SavedInputKey.Empty; + + // Check in TMPE settings + var tmpeSettingsType = typeof(KeybindSettingsBase); + var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); + + var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); + currentlyEditedBinding_.Value.TargetKey.value = saveEditingBinding; + return inTmpe; + } + + private static string FindConflictInGameSettings(InputKey sample) { + var fieldList = typeof(Settings).GetFields(BindingFlags.Static | BindingFlags.Public); + foreach (var field in fieldList) { + var customAttributes = field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; + if (customAttributes != null && customAttributes.Length > 0) { + var category = customAttributes[0].category; + if (category != string.Empty && category != "Game") { + // Ignore other categories: MapEditor, Decoration, ThemeEditor, ScenarioEditor + continue; + } + + var str = field.GetValue(null) as string; + + var savedInputKey = new SavedInputKey(str, + Settings.gameSettingsFile, + GetDefaultEntryInGameSettings(str), + true); + if (savedInputKey.value == sample) { + return (category == string.Empty ? string.Empty : (category + " -- ")) + + CamelCaseSplit(field.Name); + } + } + } + + return string.Empty; + } + + private static InputKey GetDefaultEntryInGameSettings(string entryName) { + var field = typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); + if (field == null) { + return 0; + } + + var obj = field.GetValue(null); + if (obj is InputKey) { + return (InputKey)obj; + } + + return 0; + } + + /// + /// For given key and category check TM:PE settings for the Global category + /// and the same category if it is not Global. This will allow reusing key in other tool + /// categories without conflicting. + /// + /// The key to search for + /// The category Global or some tool name + /// Fields of the key settings class + /// Empty string if no conflicts otherwise the key name to print an error + private static string FindConflictInTmpe(InputKey sample, string sampleCategory, FieldInfo[] fields) { + foreach (var field in fields) { + // This will match inputkeys of TMPE key settings + if (field.FieldType != typeof(KeybindSetting)) { + continue; + } + + var tmpeSetting = field.GetValue(null) as KeybindSetting; + + // Check category, category=Global will check keys in all categories + // category= will check Global and its own only + if (sampleCategory != "Global" + && sampleCategory != tmpeSetting.Category) { + continue; + } + + if (tmpeSetting.HasKey(sample)) { + return "TM:PE, " + + Translation.GetString("Keybind_category_" + tmpeSetting.Category) + + " -- " + CamelCaseSplit(field.Name); + } + } + + return string.Empty; + } + + private static string CamelCaseSplit(string s) { + var words = Regex.Matches(s, @"([A-Z][a-z]+)") + .Cast() + .Select(m => m.Value); + + return string.Join(" ", words.ToArray()); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 8ae7579ad..1004e092a 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -180,6 +180,7 @@ + diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs index f721e8baf..80d2b8227 100644 --- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs @@ -1,5 +1,4 @@ -using ColossalFramework; -using TrafficManager.State; +using TrafficManager.State; using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs index a997b8e8f..69639e923 100644 --- a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs @@ -1,6 +1,4 @@ -using ColossalFramework; -using TrafficManager.State; -using TrafficManager.State.Keybinds; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class LaneArrowsButton : MenuToolModeButton { diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs index b59297aff..2f7187ea0 100644 --- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs @@ -1,6 +1,4 @@ -using ColossalFramework; -using TrafficManager.State; -using TrafficManager.State.Keybinds; +using TrafficManager.State.Keybinds; namespace TrafficManager.UI.MainMenu { public class ToggleTrafficLightsButton : MenuToolModeButton { From 9f6fbbcefec5fc55334d42340f5e313f535b3204 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 30 Jun 2019 15:43:45 +0200 Subject: [PATCH 105/142] Update Main Menu tooltip; Null exception in Main Menu tooltip --- TLM/TLM/State/Keybinds/KeybindSetting.cs | 11 +++++++++++ TLM/TLM/State/Keybinds/KeybindSettingsPage.cs | 6 ++++++ TLM/TLM/State/Keybinds/KeybindUI.cs | 14 ++++++++------ TLM/TLM/UI/LinearSpriteButton.cs | 2 +- TLM/TLM/UI/UIMainMenuButton.cs | 11 ++++++++--- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/TLM/TLM/State/Keybinds/KeybindSetting.cs b/TLM/TLM/State/Keybinds/KeybindSetting.cs index b846d6fb9..59c4b74d8 100644 --- a/TLM/TLM/State/Keybinds/KeybindSetting.cs +++ b/TLM/TLM/State/Keybinds/KeybindSetting.cs @@ -29,6 +29,9 @@ public struct Editable { [CanBeNull] public SavedInputKey AlternateKey; + public delegate void OnChangedHandler(); + private OnChangedHandler onChanged_; + public KeybindSetting(string cat, string configFileKey, InputKey? defaultKey1 = null) { @@ -40,6 +43,14 @@ public KeybindSetting(string cat, true); } + public void OnChanged(OnChangedHandler onChanged) { + onChanged_ = onChanged; + } + + public void OnChanged() { + onChanged_(); + } + public KeybindSetting(string cat, string configFileKey, InputKey? defaultKey1, diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs index 1b0126ffa..d96b7e4aa 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -11,6 +11,12 @@ private void Awake() { AddKeybindRowUI(Translation.GetString("Keybind_toggle_TMPE_main_menu"), ToggleMainMenu); + ToggleMainMenu.OnChanged(() => { + if (LoadingExtension.BaseUI != null && + LoadingExtension.BaseUI.MainMenuButton != null) { + LoadingExtension.BaseUI.MainMenuButton.UpdateTooltip(); + } + }); AddKeybindRowUI(Translation.GetString("Keybind_toggle_traffic_lights_tool"), ToggleTrafficLightTool); diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index 4d23bc8be..d5a0198ac 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -107,7 +107,7 @@ public void CreateKeybindText(UIPanel parent, SavedInputKey showKey) { private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { // This will only work if the user clicked the modify button // otherwise no effect - if (currentlyEditedBinding_ == null || Keybind.IsModifierKey(p.keycode)) { + if (!currentlyEditedBinding_.HasValue || Keybind.IsModifierKey(p.keycode)) { return; } @@ -129,11 +129,12 @@ private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { false); } else { currentlyEditedBinding_.Value.TargetKey.value = inputKey; + currentlyEditedBinding_.Value.Target.OnChanged(); } // Update text on the button - var uITextComponent = p.source as UITextComponent; - uITextComponent.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + var button = p.source as UIButton; + button.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); currentlyEditedBinding_ = null; } @@ -170,11 +171,12 @@ private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { false); } else { currentlyEditedBinding_.Value.TargetKey.value = inputKey; + currentlyEditedBinding_.Value.Target.OnChanged(); } - var uIButton2 = p.source as UIButton; - uIButton2.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); - uIButton2.buttonsMask = UIMouseButton.Left; + var button = p.source as UIButton; + button.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + button.buttonsMask = UIMouseButton.Left; currentlyEditedBinding_ = null; } } diff --git a/TLM/TLM/UI/LinearSpriteButton.cs b/TLM/TLM/UI/LinearSpriteButton.cs index b771d4fb7..f793cb596 100644 --- a/TLM/TLM/UI/LinearSpriteButton.cs +++ b/TLM/TLM/UI/LinearSpriteButton.cs @@ -147,7 +147,7 @@ internal void UpdateProperties() { /// /// Tooltip to append to the main tooltip text, or an empty string private string GetShortcutTooltip() { - return ShortcutKey.Str("\n"); + return ShortcutKey != null ? ShortcutKey.Str("\n") : string.Empty; } } } \ No newline at end of file diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 352cb8f2d..303c26ea5 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -89,9 +89,14 @@ internal void SetPosLock(bool lck) { } protected override void OnClick(UIMouseEventParameter p) { - Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); - LoadingExtension.BaseUI.ToggleMainMenu(); - UpdateSprites(); + try { + Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); + LoadingExtension.BaseUI.ToggleMainMenu(); + UpdateSprites(); + } + catch (Exception e) { + Log.Error($"Toggle mainmenu failed {e}"); + } } protected override void OnPositionChanged() { From edf831a6c7eda301a2788c1c7ce26b9a0ef67b0d Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Sun, 30 Jun 2019 20:12:02 +0100 Subject: [PATCH 106/142] Formatting & added keybinds feature, etc --- CHANGELOG.md | 9 ++++--- README.md | 67 +++++++++++++++++++++++++++------------------------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2287049f..00e518fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,12 @@ - Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) - Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) - Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) +- Added: Keybinds tab in mod options - choose your own shortcuts! (thanks kvakvs) (#382) +- Added: Show keyboard shortcuts in button tooltips where applicable (thanks kvakvs) (#382) - Added: Basic support of offline mode for users playing on EA's Origin service (#333) - Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) -- Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid!) (#362) -- Fixed: Mail trucks ignoring lane arrows (#307, #338) +- Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid) (#362) +- Fixed: Mail trucks ignoring lane arrows (thanks Subaru & eudyptula for feedback) (#307, #338) - Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) - Fixed: Random parking broken (thanks s2500111 for beta testing) (#259, #359) - Fixed: Pedestrian crossing restriction affects road-side parking (#259, #359) @@ -33,7 +35,8 @@ - Meta: Added GitHub issue templates for bugs, features, translations. (#272) - Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) - Meta: Added entire `.vs/` folder to `.gitignore` (#395) -* Meta: Updated install guide to include section for EA Origin users (#333) +- Meta: Updated install guide to include section for EA Origin users (#333) +- Meta: Enable latest C# `LangVersion` in all projects (#398) ### 10.20, 21/05/2019 - Updated for game version 1.12.0-f5 diff --git a/README.md b/README.md index f14b11d9e..6c7598281 100644 --- a/README.md +++ b/README.md @@ -8,38 +8,41 @@ > Having problems with traffic despawning after updating roads or rails? Try [Broken Node Detector](https://steamcommunity.com/sharedfiles/filedetails/?id=1777173984) which helps detect a game bug. Collossal Order are aware of the issue. #### Version [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), ??/07/2019 -* Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) -* Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) -* Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) -* Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) -* Added: Basic support of offline mode for users playing on EA's Origin service (#333) -* Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) -* Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid!) (#362) -* Fixed: Mail trucks ignoring lane arrows (#307, #338) -* Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) -* Fixed: Random parking broken (thanks s2500111 for beta testing) (#259, #359) -* Fixed: Pedestrian crossing restriction affects road-side parking (#259, #359) -* Fixed: 'Vanilla Trees Remover' is now compatible (thanks TPB for fixing) (#331, #332) -* Fixed: Single-lane bunching on DLS higher than 50% (#263 #334) -* Fixed: Lane changes at toll booths (also notified CO of bug in vanilla) (#225, #355) -* Fixed: Minor issues regarding saving/loading junction restrictions (#358) -* Fixed: Changes of default junction restrictions not reflected in UI overlay (#358) -* Fixed: Resetting stuck cims unpauses the simulation (#358, #351) -* Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190) -* Fixed: TargetInvocationException in mod compatibility checker (#386, #333) -* Updated: Chinese translation (thanks Emphasia) (#375, #336) -* Updated: German translation (thanks kvakvs) (#384) -* Updated: Polish translation (thanks krzychu124) (#384, #333) -* Updated: Russian translation (thanks vitalii201) (#327, #328) -* Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) -* Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) -* Meta: Pathfinder debug logging tools (switch `26`) (#370) -* Meta: Separate binaries for Stable and Labs on GitHub release pages (#360) -* Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) -* Meta: Added GitHub issue templates for bugs, features, translations. (#272) -* Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) -* Meta: Added entire `.vs/` folder to `.gitignore` (#395) -* Meta: Updated install guide to include section for EA Origin users (#333) +- Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) +- Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) +- Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) +- Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) +- Added: Keybinds tab in mod options - choose your own shortcuts! (thanks kvakvs) (#382) +- Added: Show keyboard shortcuts in button tooltips where applicable (thanks kvakvs) (#382) +- Added: Basic support of offline mode for users playing on EA's Origin service (#333) +- Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) +- Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid) (#362) +- Fixed: Mail trucks ignoring lane arrows (thanks Subaru & eudyptula for feedback) (#307, #338) +- Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) +- Fixed: Random parking broken (thanks s2500111 for beta testing) (#259, #359) +- Fixed: Pedestrian crossing restriction affects road-side parking (#259, #359) +- Fixed: 'Vanilla Trees Remover' is now compatible (thanks TPB for fixing) (#331, #332) +- Fixed: Single-lane bunching on DLS higher than 50% (#263 #334) +- Fixed: Lane changes at toll booths (also notified CO of bug in vanilla) (#225, #355) +- Fixed: Minor issues regarding saving/loading junction restrictions (#358) +- Fixed: Changes of default junction restrictions not reflected in UI overlay (#358) +- Fixed: Resetting stuck cims unpauses the simulation (#358, #351) +- Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190) +- Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +- Updated: Chinese translation (thanks Emphasia) (#375, #336) +- Updated: German translation (thanks kvakvs) (#384) +- Updated: Polish translation (thanks krzychu124) (#384, #333) +- Updated: Russian translation (thanks vitalii201) (#327, #328) +- Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) +- Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) +- Meta: Pathfinder debug logging tools (switch `26`) (#370) +- Meta: Separate binaries for Stable and Labs on GitHub release pages (#360) +- Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) +- Meta: Added GitHub issue templates for bugs, features, translations. (#272) +- Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) +- Meta: Added entire `.vs/` folder to `.gitignore` (#395) +- Meta: Updated install guide to include section for EA Origin users (#333) +- Meta: Enable latest C# `LangVersion` in all projects (#398) See [Full Changelog](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/blob/master/CHANGELOG.md) for details of earlier releases. From 4af528781f5bdb3ba582c48ea5f9e6c1fa6a0aa1 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 30 Jun 2019 21:43:13 +0200 Subject: [PATCH 107/142] Groups for keybinds settings; Dual settings displayed in 1 row --- TLM/TLM/State/Keybinds/KeybindSetting.cs | 20 ++- TLM/TLM/State/Keybinds/KeybindSettingsBase.cs | 21 ++- TLM/TLM/State/Keybinds/KeybindSettingsPage.cs | 30 +++- TLM/TLM/State/Keybinds/KeybindUI.cs | 149 ++++++++++++++---- 4 files changed, 162 insertions(+), 58 deletions(-) diff --git a/TLM/TLM/State/Keybinds/KeybindSetting.cs b/TLM/TLM/State/Keybinds/KeybindSetting.cs index 59c4b74d8..226db9410 100644 --- a/TLM/TLM/State/Keybinds/KeybindSetting.cs +++ b/TLM/TLM/State/Keybinds/KeybindSetting.cs @@ -24,13 +24,17 @@ public struct Editable { /// /// The key itself, bound to a config file value /// - public SavedInputKey Key; + public SavedInputKey Key { get; } + /// + /// A second key, which can possibly be used or kept null + /// [CanBeNull] - public SavedInputKey AlternateKey; + public SavedInputKey AlternateKey { get; } + + private OnKeyChangedHandler onKeyChanged_; - public delegate void OnChangedHandler(); - private OnChangedHandler onChanged_; + public delegate void OnKeyChangedHandler(); public KeybindSetting(string cat, string configFileKey, @@ -43,12 +47,12 @@ public KeybindSetting(string cat, true); } - public void OnChanged(OnChangedHandler onChanged) { - onChanged_ = onChanged; + public void OnKeyChanged(OnKeyChangedHandler onChanged) { + onKeyChanged_ = onChanged; } - public void OnChanged() { - onChanged_(); + public void NotifyKeyChanged() { + onKeyChanged_(); } public KeybindSetting(string cat, diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs index 4948ee57f..22255eb6a 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs @@ -59,7 +59,7 @@ private static void OnMainMenuShortcutChanged(SavedInputKey savedinputkey) { SavedInputKey.Encode(KeyCode.Delete, false, false, false), SavedInputKey.Encode(KeyCode.Backspace, false, false, false)); - private KeybindUI keybindUi_ = new KeybindUI(); + protected KeybindUI keybindUi_ = new KeybindUI(); /// /// Counter to produce alternating UI row colors (dark and light). @@ -79,10 +79,6 @@ protected static void TryCreateConfig() { } } - protected void BeginForm() { - keybindUi_.BeginForm(component); - } - /// /// Creates a row in the current panel with the label and the button /// which will prompt user to press a new key. @@ -95,8 +91,8 @@ protected void AddKeybindRowUI(string label, KeybindSetting keybind) { settingsRow.backgroundSprite = null; } - keybindUi_.CreateLabel(settingsRow, label); - keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key); + keybindUi_.CreateLabel(settingsRow, label, 0.6f); + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key, 0.3f); } /// @@ -104,15 +100,16 @@ protected void AddKeybindRowUI(string label, KeybindSetting keybind) { /// previous key editor. /// /// - protected void AddAlternateUiControl(KeybindSetting keybind) { + protected void AddAlternateKeybindUI(string title, KeybindSetting keybind) { var settingsRow = keybindUi_.CreateRowPanel(); if (uiRowCount_ % 2 == 1) { // color the panel but do not increment uiRowCount settingsRow.backgroundSprite = null; } - keybindUi_.CreateLabel(settingsRow, string.Empty); - keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.AlternateKey); + keybindUi_.CreateLabel(settingsRow, title, 0.45f); + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key, 0.2f); + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.AlternateKey, 0.2f); } /// @@ -121,13 +118,13 @@ protected void AddAlternateUiControl(KeybindSetting keybind) { /// /// Localized label /// The setting to edit - protected void ReadOnlyKeybindUI(string label, KeybindSetting keybind) { + protected void AddReadOnlyKeybind(string label, KeybindSetting keybind) { var settingsRow = keybindUi_.CreateRowPanel(); if (uiRowCount_++ % 2 == 1) { settingsRow.backgroundSprite = null; } - keybindUi_.CreateLabel(settingsRow, label); + keybindUi_.CreateLabel(settingsRow, label, 0.6f); keybindUi_.CreateKeybindText(settingsRow, keybind.Key); } diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs index d96b7e4aa..e9a22b74d 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -5,13 +5,24 @@ public class KeybindSettingsPage : KeybindSettingsBase { private void Awake() { TryCreateConfig(); - BeginForm(); - ReadOnlyKeybindUI(Translation.GetString("Keybind_Exit_subtool"), - ToolCancelViewOnly); + keybindUi_.BeginForm(component); + keybindUi_.AddGroup(Translation.GetString("Keybind_category_Global"), + CreateUI_Global); + // New section: Lane Connector Tool + keybindUi_.AddGroup(Translation.GetString("Keybind_category_LaneConnector"), + CreateUI_LaneConnector); + } + + /// + /// Fill Global keybinds section + /// + private void CreateUI_Global() { + AddReadOnlyKeybind(Translation.GetString("Keybind_Exit_subtool"), + ToolCancelViewOnly); AddKeybindRowUI(Translation.GetString("Keybind_toggle_TMPE_main_menu"), ToggleMainMenu); - ToggleMainMenu.OnChanged(() => { + ToggleMainMenu.OnKeyChanged(() => { if (LoadingExtension.BaseUI != null && LoadingExtension.BaseUI.MainMenuButton != null) { LoadingExtension.BaseUI.MainMenuButton.UpdateTooltip(); @@ -30,14 +41,17 @@ private void Awake() { JunctionRestrictionsTool); AddKeybindRowUI(Translation.GetString("Keybind_use_speed_limits_tool"), SpeedLimitsTool); + } - // New section: Lane Connector Tool + /// + /// Fill Lane Connector keybinds section + /// + private void CreateUI_LaneConnector() { AddKeybindRowUI(Translation.GetString("Keybind_lane_connector_stay_in_lane"), LaneConnectorStayInLane); - ReadOnlyKeybindUI(Translation.GetString("Keybind_lane_connector_delete"), - LaneConnectorDelete); - AddAlternateUiControl(LaneConnectorDelete); + AddAlternateKeybindUI(Translation.GetString("Keybind_lane_connector_delete"), + LaneConnectorDelete); } } } \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index d5a0198ac..c261d6ac4 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -1,3 +1,5 @@ +using System; + namespace TrafficManager.State.Keybinds { using System.Linq; using System.Reflection; @@ -11,18 +13,20 @@ namespace TrafficManager.State.Keybinds { /// Helper for creating keyboard bindings Settings page. /// public class KeybindUI { - private KeybindSetting.Editable? currentlyEditedBinding_; - private const float ROW_WIDTH = 744f; + private const float ROW_WIDTH = 744f - 15f; private const float ROW_HEIGHT = 34f; + private KeybindSetting.Editable? currentlyEditedBinding_; + /// - /// Scrollable panel for keybinds, first created on Unity Awake call + /// Scrollable panel, first created on Unity Awake call /// - private UIComponent settingsPanel_; + private UIComponent scrollPanel_; - public void BeginForm(UIComponent component) { - settingsPanel_ = CreateScrollablePanel(component); - } + /// + /// Group panel with text title for adding controls in it + /// + private UIComponent currentGroup_; /// /// Creates a row for keyboard bindings editor. The row will contain a text @@ -33,40 +37,102 @@ public void BeginForm(UIComponent component) { public static UIComponent CreateScrollablePanel(UIComponent root) { var scrollablePanel = root.AddUIComponent(); scrollablePanel.backgroundSprite = string.Empty; - scrollablePanel.size = new Vector2(ROW_WIDTH, 0); + scrollablePanel.size = root.size; scrollablePanel.relativePosition = Vector3.zero; + scrollablePanel.clipChildren = true; scrollablePanel.autoLayoutStart = LayoutStart.TopLeft; scrollablePanel.autoLayoutDirection = LayoutDirection.Vertical; scrollablePanel.autoLayout = true; + scrollablePanel.FitTo(root); + scrollablePanel.scrollWheelDirection = UIOrientation.Vertical; + scrollablePanel.builtinKeyNavigation = true; + + UIScrollbar verticalScroll = root.AddUIComponent(); + verticalScroll.stepSize = 1; + verticalScroll.relativePosition = new Vector2(root.width - 15, 0); + verticalScroll.orientation = UIOrientation.Vertical; + verticalScroll.size = new Vector2(20, root.height); + verticalScroll.incrementAmount = 25; + verticalScroll.scrollEasingType = EasingType.BackEaseOut; + + scrollablePanel.verticalScrollbar = verticalScroll; + + UISlicedSprite track = verticalScroll.AddUIComponent(); + track.spriteName = "ScrollbarTrack"; + track.relativePosition = Vector3.zero; + track.size = new Vector2(16, 320); + + verticalScroll.trackObject = track; + + UISlicedSprite thumb = track.AddUIComponent(); + thumb.spriteName = "ScrollbarThumb"; + thumb.autoSize = true; + thumb.relativePosition = Vector3.zero; + verticalScroll.thumbObject = thumb; + return scrollablePanel; } + public void BeginForm(UIComponent component) { + scrollPanel_ = CreateScrollablePanel(component); + } + + /// + /// Create an empty row of ROW_HEIGHT pixels, with left-to-right layout + /// + /// The row panel public UIPanel CreateRowPanel() { - settingsPanel_.size += new Vector2(0f, ROW_HEIGHT); + // scrollPanel_.size += new Vector2(0f, ROW_HEIGHT); + var rowPanel = currentGroup_.AddUIComponent(); + rowPanel.size = new Vector2(ROW_WIDTH, ROW_HEIGHT); + rowPanel.autoLayoutStart = LayoutStart.TopLeft; + rowPanel.autoLayoutDirection = LayoutDirection.Horizontal; + rowPanel.autoLayout = true; + + return rowPanel; + } - var p = settingsPanel_.AddUIComponent(); - p.size = new Vector2(ROW_WIDTH, ROW_HEIGHT); - p.autoLayoutStart = LayoutStart.TopLeft; - p.autoLayoutDirection = LayoutDirection.Horizontal; - p.autoLayout = true; + /// + /// Create a box with title + /// + /// Title + private void BeginGroup(string text) { + const string K_GROUP_TEMPLATE = "OptionsGroupTemplate"; + var groupPanel = scrollPanel_.AttachUIComponent( + UITemplateManager.GetAsGameObject(K_GROUP_TEMPLATE)) as UIPanel; + groupPanel.autoLayoutStart = LayoutStart.TopLeft; + groupPanel.autoLayoutDirection = LayoutDirection.Vertical; + groupPanel.autoLayout = true; + + groupPanel.Find("Label").text = text; + + currentGroup_ = groupPanel.Find("Content"); + } - return p; + /// + /// Close the group and expand the scroll panel to include it + /// + private void EndGroup() { + // scrollPanel_.size += new Vector2(0f, currentGroup_.size.y); + currentGroup_ = null; } - public void CreateLabel(UIPanel parent, string text) { + public UILabel CreateLabel(UIPanel parent, string text, float widthFraction) { var label = parent.AddUIComponent(); label.autoSize = false; - label.size = new Vector2(ROW_WIDTH * 0.6f, ROW_HEIGHT); + label.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); label.text = text; label.verticalAlignment = UIVerticalAlignment.Middle; label.textAlignment = UIHorizontalAlignment.Left; + return label; } - public void CreateKeybindButton(UIPanel parent, KeybindSetting setting, SavedInputKey editKey) { + public void CreateKeybindButton(UIPanel parent, KeybindSetting setting, SavedInputKey editKey, + float widthFraction) { var btn = parent.AddUIComponent(); - btn.size = new Vector2(ROW_WIDTH * 0.3f, ROW_HEIGHT); + btn.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); btn.text = Keybind.Str(editKey); btn.hoveredTextColor = new Color32(128, 128, 255, 255); // darker blue btn.pressedTextColor = new Color32(192, 192, 255, 255); // lighter blue @@ -75,17 +141,28 @@ public void CreateKeybindButton(UIPanel parent, KeybindSetting setting, SavedInp btn.eventKeyDown += OnBindingKeyDown; btn.eventMouseDown += OnBindingMouseDown; btn.objectUserData - = new KeybindSetting.Editable { Target = setting, TargetKey = editKey }; + = new KeybindSetting.Editable {Target = setting, TargetKey = editKey}; - // Add X button + AddXButton(parent, editKey, btn); + } + + /// + /// Add X button to the right of another button + /// + /// The panel to host the new button + /// The key to be cleared on click + /// Align X button to the right of this + private static void AddXButton(UIPanel parent, SavedInputKey editKey, UIButton alignTo) { var btnX = parent.AddUIComponent(); btnX.autoSize = false; btnX.size = new Vector2(ROW_HEIGHT, ROW_HEIGHT); - btnX.normalBgSprite = "ButtonMenu"; + btnX.normalBgSprite = "buttonclose"; + btnX.hoveredBgSprite = "buttonclosehover"; + btnX.pressedBgSprite = "buttonclosepressed"; btnX.text = "X"; btnX.eventClicked += (component, eventParam) => { editKey.value = SavedInputKey.Empty; - btn.text = Keybind.Str(editKey); + alignTo.text = Keybind.Str(editKey); }; } @@ -104,6 +181,17 @@ public void CreateKeybindText(UIPanel parent, SavedInputKey showKey) { label.textColor = new Color32(128, 128, 128, 255); // grey } + /// + /// Performs group creation sequence: BeginGroup, add keybinds UI rows, EndGroup + /// + /// Translated title + /// Function which adds keybind rows + public void AddGroup(string title, Action code) { + BeginGroup(title); + code.Invoke(); + EndGroup(); + } + private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { // This will only work if the user clicked the modify button // otherwise no effect @@ -118,7 +206,7 @@ private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { ? currentlyEditedBinding_.Value.TargetKey : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); - var editable = (KeybindSetting.Editable)p.source.objectUserData; + var editable = (KeybindSetting.Editable) p.source.objectUserData; var category = editable.Target.Category; var maybeConflict = FindConflict(inputKey, category); @@ -129,7 +217,7 @@ private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { false); } else { currentlyEditedBinding_.Value.TargetKey.value = inputKey; - currentlyEditedBinding_.Value.Target.OnChanged(); + currentlyEditedBinding_.Value.Target.NotifyKeyChanged(); } // Update text on the button @@ -139,7 +227,7 @@ private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { } private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { - var editable = (KeybindSetting.Editable)p.source.objectUserData; + var editable = (KeybindSetting.Editable) p.source.objectUserData; // This will only work if the user is not in the process of changing the shortcut if (currentlyEditedBinding_ == null) { @@ -171,7 +259,7 @@ private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { false); } else { currentlyEditedBinding_.Value.TargetKey.value = inputKey; - currentlyEditedBinding_.Value.Target.OnChanged(); + currentlyEditedBinding_.Value.Target.NotifyKeyChanged(); } var button = p.source as UIButton; @@ -181,7 +269,7 @@ private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { } } - /// + /// /// For an inputkey, try find where possibly it is already used. /// This covers game Settings class, and self (OptionsKeymapping class). /// @@ -215,7 +303,8 @@ private string FindConflict(InputKey sample, string sampleCategory) { private static string FindConflictInGameSettings(InputKey sample) { var fieldList = typeof(Settings).GetFields(BindingFlags.Static | BindingFlags.Public); foreach (var field in fieldList) { - var customAttributes = field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; + var customAttributes = + field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; if (customAttributes != null && customAttributes.Length > 0) { var category = customAttributes[0].category; if (category != string.Empty && category != "Game") { @@ -247,7 +336,7 @@ private static InputKey GetDefaultEntryInGameSettings(string entryName) { var obj = field.GetValue(null); if (obj is InputKey) { - return (InputKey)obj; + return (InputKey) obj; } return 0; From 26e868a4ece02113f81dd1b397beb834d13dfa51 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Sun, 30 Jun 2019 22:04:24 +0200 Subject: [PATCH 108/142] Clear lane connections now has first key readonly (Delete) --- TLM/TLM/State/Keybinds/KeybindSettingsBase.cs | 20 +++++++++++++++---- TLM/TLM/State/Keybinds/KeybindSettingsPage.cs | 3 ++- TLM/TLM/State/Keybinds/KeybindUI.cs | 12 +++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs index 22255eb6a..1944448f2 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs @@ -100,7 +100,10 @@ protected void AddKeybindRowUI(string label, KeybindSetting keybind) { /// previous key editor. /// /// - protected void AddAlternateKeybindUI(string title, KeybindSetting keybind) { + /// Whether main key binding is editable or readonly + /// Whether alt key binding is editable or readonly + protected void AddAlternateKeybindUI(string title, KeybindSetting keybind, + bool editable1, bool editable2) { var settingsRow = keybindUi_.CreateRowPanel(); if (uiRowCount_ % 2 == 1) { // color the panel but do not increment uiRowCount @@ -108,8 +111,17 @@ protected void AddAlternateKeybindUI(string title, KeybindSetting keybind) { } keybindUi_.CreateLabel(settingsRow, title, 0.45f); - keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key, 0.2f); - keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.AlternateKey, 0.2f); + if (editable1) { + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key, 0.2f); + } else { + keybindUi_.CreateKeybindText(settingsRow, keybind.Key, 0.25f); + } + + if (editable2) { + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.AlternateKey, 0.2f); + } else { + keybindUi_.CreateKeybindText(settingsRow, keybind.AlternateKey, 0.25f); + } } /// @@ -125,7 +137,7 @@ protected void AddReadOnlyKeybind(string label, KeybindSetting keybind) { } keybindUi_.CreateLabel(settingsRow, label, 0.6f); - keybindUi_.CreateKeybindText(settingsRow, keybind.Key); + keybindUi_.CreateKeybindText(settingsRow, keybind.Key, 0.3f); } protected void OnEnable() { diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs index e9a22b74d..7a880fe89 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -50,8 +50,9 @@ private void CreateUI_LaneConnector() { AddKeybindRowUI(Translation.GetString("Keybind_lane_connector_stay_in_lane"), LaneConnectorStayInLane); + // First key binding is readonly (editable1=false) AddAlternateKeybindUI(Translation.GetString("Keybind_lane_connector_delete"), - LaneConnectorDelete); + LaneConnectorDelete, false, true); } } } \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index c261d6ac4..0bd94e5d5 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -49,7 +49,7 @@ public static UIComponent CreateScrollablePanel(UIComponent root) { scrollablePanel.scrollWheelDirection = UIOrientation.Vertical; scrollablePanel.builtinKeyNavigation = true; - UIScrollbar verticalScroll = root.AddUIComponent(); + var verticalScroll = root.AddUIComponent(); verticalScroll.stepSize = 1; verticalScroll.relativePosition = new Vector2(root.width - 15, 0); verticalScroll.orientation = UIOrientation.Vertical; @@ -59,14 +59,14 @@ public static UIComponent CreateScrollablePanel(UIComponent root) { scrollablePanel.verticalScrollbar = verticalScroll; - UISlicedSprite track = verticalScroll.AddUIComponent(); + var track = verticalScroll.AddUIComponent(); track.spriteName = "ScrollbarTrack"; track.relativePosition = Vector3.zero; track.size = new Vector2(16, 320); verticalScroll.trackObject = track; - UISlicedSprite thumb = track.AddUIComponent(); + var thumb = track.AddUIComponent(); thumb.spriteName = "ScrollbarThumb"; thumb.autoSize = true; thumb.relativePosition = Vector3.zero; @@ -84,7 +84,6 @@ public void BeginForm(UIComponent component) { /// /// The row panel public UIPanel CreateRowPanel() { - // scrollPanel_.size += new Vector2(0f, ROW_HEIGHT); var rowPanel = currentGroup_.AddUIComponent(); rowPanel.size = new Vector2(ROW_WIDTH, ROW_HEIGHT); rowPanel.autoLayoutStart = LayoutStart.TopLeft; @@ -115,7 +114,6 @@ private void BeginGroup(string text) { /// Close the group and expand the scroll panel to include it /// private void EndGroup() { - // scrollPanel_.size += new Vector2(0f, currentGroup_.size.y); currentGroup_ = null; } @@ -171,10 +169,10 @@ private static void AddXButton(UIPanel parent, SavedInputKey editKey, UIButton a /// /// The panel to host it /// The key to display - public void CreateKeybindText(UIPanel parent, SavedInputKey showKey) { + public void CreateKeybindText(UIPanel parent, SavedInputKey showKey, float widthFraction) { var label = parent.AddUIComponent(); label.autoSize = false; - label.size = new Vector2(ROW_WIDTH * 0.3f, ROW_HEIGHT); + label.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); label.text = Keybind.Str(showKey); label.verticalAlignment = UIVerticalAlignment.Middle; label.textAlignment = UIHorizontalAlignment.Center; From 7ab14175e02481b45047ff59370f8c1d1bd5ae37 Mon Sep 17 00:00:00 2001 From: krzychu124 Date: Sun, 30 Jun 2019 22:09:31 +0200 Subject: [PATCH 109/142] Updated langVersion value to latest --- TLM/TLM/TLM.csproj | 2 +- TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj | 2 +- TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj | 2 +- TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj | 2 +- TLM/TMPE.UnitTest/TMPE.UnitTest.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index c08ca2a3f..44cc70949 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -12,7 +12,7 @@ v3.5 512 - 7.3 + latest true diff --git a/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj b/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj index 2d1705a34..e2a9c6202 100644 --- a/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj +++ b/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj @@ -12,7 +12,7 @@ v3.5 512 - 7.3 + latest true diff --git a/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj b/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj index fe169164f..240c9d556 100644 --- a/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj +++ b/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj @@ -12,7 +12,7 @@ v3.5 512 - 7.3 + latest true diff --git a/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj b/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj index cb883f46a..d78f66d98 100644 --- a/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj +++ b/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj @@ -12,7 +12,7 @@ v3.5 512 - 7.3 + latest true diff --git a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj index aa57d72f9..4527aa17e 100644 --- a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj +++ b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj @@ -17,7 +17,7 @@ False UnitTest - 7.3 + latest true From eafc7ca583d16d7070f7fa113bddafcc3cc69155 Mon Sep 17 00:00:00 2001 From: krzychu124 Date: Sun, 30 Jun 2019 23:15:03 +0200 Subject: [PATCH 110/142] Updated langVersion value to latest --- TLM/CSUtil.Commons/CSUtil.Commons.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/TLM/CSUtil.Commons/CSUtil.Commons.csproj b/TLM/CSUtil.Commons/CSUtil.Commons.csproj index f51ceffd9..6e17d5df9 100644 --- a/TLM/CSUtil.Commons/CSUtil.Commons.csproj +++ b/TLM/CSUtil.Commons/CSUtil.Commons.csproj @@ -12,6 +12,7 @@ v3.5 512 + latest true From a33333ca08cf922b3380af55101f776c39d2e9ec Mon Sep 17 00:00:00 2001 From: krzychu124 Date: Mon, 1 Jul 2019 21:06:44 +0200 Subject: [PATCH 111/142] Vehicle limit fix after incomplete merge --- TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs | 10 +++++----- TLM/TLM/Manager/Impl/ExtVehicleManager.cs | 8 ++++---- TLM/TLM/Manager/Impl/TrafficPriorityManager.cs | 4 ++-- TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs | 2 +- TLM/TLM/Traffic/Impl/SegmentEnd.cs | 10 +++++----- TLM/TLM/UI/TrafficManagerTool.cs | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs b/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs index badb06356..aad7661f1 100644 --- a/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs +++ b/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs @@ -20,7 +20,7 @@ public class ExtSegmentEndManager : AbstractCustomManager, IExtSegmentEndManager static ExtSegmentEndManager() { Instance = new ExtSegmentEndManager(); } - + /// /// All additional data for segment ends /// @@ -44,7 +44,7 @@ public string GenerateVehicleChainDebugInfo(ushort segmentId, bool startNode) { ret += $" -> {vehicleId} (seg: {Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].currentSegmentId}@{Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].currentStartNode} , adj: {Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].previousVehicleIdOnSegment}..{Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment})"; vehicleId = Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -64,7 +64,7 @@ protected void Reset(ref ExtSegmentEnd extSegmentEnd) { while (extSegmentEnd.firstVehicleId != 0) { extVehicleMan.Unlink(ref extVehicleMan.ExtVehicles[extSegmentEnd.firstVehicleId]); - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -111,7 +111,7 @@ public uint GetRegisteredVehicleCount(ref ExtSegmentEnd end) { ++ret; vehicleId = vehStateManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -150,7 +150,7 @@ public ArrowDirection GetDirection(ref ExtSegmentEnd sourceEnd, ushort targetSeg protected ArrowDirection CalculateArrowDirection(Vector3 sourceDir, Vector3 targetDir) { sourceDir.y = 0; sourceDir.Normalize(); - + targetDir.y = 0; targetDir.Normalize(); float c = Vector3.Cross(sourceDir, targetDir).y; diff --git a/TLM/TLM/Manager/Impl/ExtVehicleManager.cs b/TLM/TLM/Manager/Impl/ExtVehicleManager.cs index 213699760..885be306c 100644 --- a/TLM/TLM/Manager/Impl/ExtVehicleManager.cs +++ b/TLM/TLM/Manager/Impl/ExtVehicleManager.cs @@ -43,8 +43,8 @@ protected override void InternalPrintDebugInfo() { } private ExtVehicleManager() { - ExtVehicles = new ExtVehicle[VehicleManager.MAX_VEHICLE_COUNT]; - for (uint i = 0; i < VehicleManager.MAX_VEHICLE_COUNT; ++i) { + ExtVehicles = new ExtVehicle[Constants.ServiceFactory.VehicleService.MaxVehicleCount]; + for (uint i = 0; i < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++i) { ExtVehicles[i] = new ExtVehicle((ushort)i); } } @@ -737,7 +737,7 @@ public void InitAllVehicles() { Log._Debug("ExtVehicleManager: InitAllVehicles()"); VehicleManager vehicleManager = Singleton.instance; - for (uint vehicleId = 0; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { + for (uint vehicleId = 0; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++vehicleId) { Services.VehicleService.ProcessVehicle((ushort)vehicleId, delegate (ushort vId, ref Vehicle vehicle) { if ((vehicle.m_flags & Vehicle.Flags.Created) == 0) { return true; @@ -754,7 +754,7 @@ public void InitAllVehicles() { } OnSpawnVehicle(vId, ref vehicle); - + return true; }); } diff --git a/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs b/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs index 808775a0d..a6d714b0a 100644 --- a/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs @@ -355,7 +355,7 @@ public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Posi #endif return true; } - + PriorityType curSign = GetPrioritySign(curPos.m_segment, startNode); if (curSign == PriorityType.None) { #if DEBUG @@ -451,7 +451,7 @@ public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Posi // check next incoming vehicle incomingVehicleId = vehStateManager.ExtVehicles[incomingVehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } diff --git a/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs b/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs index b1d8e3afc..d0a7b4f37 100644 --- a/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs +++ b/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs @@ -15,7 +15,7 @@ public static class CreateVehiclePatch { /// [HarmonyPrefix] public static bool Prefix(VehicleManager __instance, ref ushort vehicle, VehicleInfo info) { - if (__instance.m_vehicleCount > VehicleManager.MAX_VEHICLE_COUNT - 5) { + if (__instance.m_vehicleCount > Constants.ServiceFactory.VehicleService.MaxVehicleCount - 5) { // prioritize service vehicles and public transport when hitting the vehicle limit ItemClass.Service service = info.GetService(); if (service == ItemClass.Service.Residential || service == ItemClass.Service.Industrial || service == ItemClass.Service.Commercial || service == ItemClass.Service.Office) { diff --git a/TLM/TLM/Traffic/Impl/SegmentEnd.cs b/TLM/TLM/Traffic/Impl/SegmentEnd.cs index 507cf6775..b0fc8fc7c 100644 --- a/TLM/TLM/Traffic/Impl/SegmentEnd.cs +++ b/TLM/TLM/Traffic/Impl/SegmentEnd.cs @@ -71,7 +71,7 @@ public override string ToString() { public SegmentEnd(ushort segmentId, bool startNode) : base(segmentId, startNode) { Update(); } - + ~SegmentEnd() { //Destroy(); } @@ -119,7 +119,7 @@ public IDictionary[] MeasureOutgoingVehicles(bool includeStopped=t }); vehicleId = vehStateManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -180,7 +180,7 @@ protected void MeasureOutgoingVehicle(bool debug, IDictionary[] re return; } - + uint normLength = 10u; if (avgSegmentLength > 0) { normLength = Math.Min(100u, (uint)(Math.Max(1u, state.totalLength) * 100u) / avgSegmentLength) + 1; // TODO +1 because the vehicle length calculation for trains/monorail in the method VehicleState.OnVehicleSpawned returns 0 (or a very small number maybe?) @@ -219,7 +219,7 @@ private void UnregisterAllVehicles() { int numIter = 0; while (segEndMan.ExtSegmentEnds[endIndex].firstVehicleId != 0) { extVehicleMan.Unlink(ref extVehicleMan.ExtVehicles[segEndMan.ExtSegmentEnds[endIndex].firstVehicleId]); - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -269,7 +269,7 @@ private void RebuildVehicleNumDicts(ref NetNode node) { if (!segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segId, (bool)Constants.ServiceFactory.NetService.IsStartNode(segId, NodeId))].outgoing) { continue; } - + foreach (TinyDictionary numVehiclesMovingToSegId in numVehiclesMovingToSegmentId) { numVehiclesMovingToSegId[segId] = 0; } diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 374aa5b4c..e75df32f2 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -905,7 +905,7 @@ private void _guiVehicles() { int startVehicleId = 1; - int endVehicleId = (int)(VehicleManager.MAX_VEHICLE_COUNT - 1); + int endVehicleId = (int)(Constants.ServiceFactory.VehicleService.MaxVehicleCount - 1); #if DEBUG if (GlobalConfig.Instance.Debug.VehicleId != 0) { startVehicleId = endVehicleId = GlobalConfig.Instance.Debug.VehicleId; From 59277cdffb51319d6f5160b83a0bdae9cd6a18d9 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 1 Jul 2019 21:45:36 +0200 Subject: [PATCH 112/142] Fixed hanging Press any key state on keybind buttons --- .gitignore | 3 +- TLM/TLM/State/Keybinds/KeybindSettingsPage.cs | 5 +- TLM/TLM/State/Keybinds/KeybindUI.cs | 78 ++++++++++++------- 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 23d1fd77d..4c81b0cbf 100644 --- a/.gitignore +++ b/.gitignore @@ -183,8 +183,9 @@ UpgradeLog*.htm FakesAssemblies/ /logo/ -# MSVS 2017 artifacts +# MSVS 2017, IntelliJ IDEA/Rider artifacts .vs/ +.idea/ # Dependecies game dlls /TLM/dependencies diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs index 7a880fe89..4a6301bb8 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -6,9 +6,12 @@ private void Awake() { TryCreateConfig(); keybindUi_.BeginForm(component); + + // Section: Global keybindUi_.AddGroup(Translation.GetString("Keybind_category_Global"), CreateUI_Global); - // New section: Lane Connector Tool + + // Section: Lane Connector Tool keybindUi_.AddGroup(Translation.GetString("Keybind_category_LaneConnector"), CreateUI_LaneConnector); } diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index 0bd94e5d5..0427a5d2b 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -157,7 +157,6 @@ private static void AddXButton(UIPanel parent, SavedInputKey editKey, UIButton a btnX.normalBgSprite = "buttonclose"; btnX.hoveredBgSprite = "buttonclosehover"; btnX.pressedBgSprite = "buttonclosepressed"; - btnX.text = "X"; btnX.eventClicked += (component, eventParam) => { editKey.value = SavedInputKey.Empty; alignTo.text = Keybind.Str(editKey); @@ -198,52 +197,58 @@ private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { } p.Use(); // Consume the event + + var editedBinding = currentlyEditedBinding_; // will be nulled by popmodal UIView.PopModal(); - var keycode = p.keycode; - var inputKey = (p.keycode == KeyCode.Escape) - ? currentlyEditedBinding_.Value.TargetKey - : SavedInputKey.Encode(keycode, p.control, p.shift, p.alt); + currentlyEditedBinding_ = editedBinding; - var editable = (KeybindSetting.Editable) p.source.objectUserData; - var category = editable.Target.Category; - - var maybeConflict = FindConflict(inputKey, category); - if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); - } else { - currentlyEditedBinding_.Value.TargetKey.value = inputKey; - currentlyEditedBinding_.Value.Target.NotifyKeyChanged(); + var keybindButton = p.source as UIButton; + + if (p.keycode != KeyCode.Escape) { + var inputKey = SavedInputKey.Encode(p.keycode, p.control, p.shift, p.alt); + var editable = (KeybindSetting.Editable) p.source.objectUserData; + var category = editable.Target.Category; + + var maybeConflict = FindConflict(inputKey, category); + if (maybeConflict != string.Empty) { + UIView.library.ShowModal("ExceptionPanel").SetMessage( + "Key Conflict", + Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, + false); + } else { + currentlyEditedBinding_.Value.TargetKey.value = inputKey; + currentlyEditedBinding_.Value.Target.NotifyKeyChanged(); + } } // Update text on the button - var button = p.source as UIButton; - button.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); - currentlyEditedBinding_ = null; + EndButtonEditMode(keybindButton); } private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { var editable = (KeybindSetting.Editable) p.source.objectUserData; + var keybindButton = p.source as UIButton; // This will only work if the user is not in the process of changing the shortcut if (currentlyEditedBinding_ == null) { p.Use(); currentlyEditedBinding_ = editable; - var uIButton = p.source as UIButton; - uIButton.buttonsMask = + keybindButton.buttonsMask = UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | UIMouseButton.Special3; - uIButton.text = "Press any key"; - p.source.Focus(); - UIView.PushModal(p.source); + keybindButton.text = "Press any key"; + keybindButton.Focus(); + UIView.PushModal(keybindButton, OnKeybindModalPopped); } else if (!Keybind.IsUnbindableMouseButton(p.buttons)) { // This will work if the user clicks while the shortcut change is in progress p.Use(); + + var editedBinding = currentlyEditedBinding_; // it will be nulled on modal pop UIView.PopModal(); + currentlyEditedBinding_ = editedBinding; + var inputKey = SavedInputKey.Encode(Keybind.ButtonToKeycode(p.buttons), Keybind.IsControlDown(), Keybind.IsShiftDown(), @@ -260,11 +265,26 @@ private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { currentlyEditedBinding_.Value.Target.NotifyKeyChanged(); } - var button = p.source as UIButton; - button.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); - button.buttonsMask = UIMouseButton.Left; - currentlyEditedBinding_ = null; + keybindButton.buttonsMask = UIMouseButton.Left; + EndButtonEditMode(keybindButton); + } + } + + /// + /// Called by the UIView when modal was popped without us knowing + /// + /// The button which temporarily was modal + private void OnKeybindModalPopped(UIComponent component) { + if (!(component is UIButton) || currentlyEditedBinding_ == null) { + return; } + + EndButtonEditMode((UIButton) component); + } + + private void EndButtonEditMode(UIButton b) { + b.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + currentlyEditedBinding_ = null; } /// From 87eda101ed6032a452d36a94dec93c22f7ae726c Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 1 Jul 2019 21:55:47 +0100 Subject: [PATCH 113/142] Attempting to exclude self from offline TMPE mod check Not working --- TLM/TLM/TrafficManagerMod.cs | 6 ++-- TLM/TLM/Util/ModsCompatibilityChecker.cs | 35 ++++++++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 83d7921ba..24f4969d2 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -17,6 +17,8 @@ public class TrafficManagerMod : IUserMod { public static readonly uint GameVersionC = 0u; public static readonly uint GameVersionBuild = 5u; + public static int Build => Assembly.GetExecutingAssembly().GetName().Version.Build; + // Note: `Version` is also used in UI/MainMenu/VersionLabel.cs public static readonly string Version = "10.20"; @@ -32,7 +34,7 @@ public class TrafficManagerMod : IUserMod { public string Description => "Manage your city's traffic"; - public void OnEnabled() { + public void OnEnabled() { Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} {Branch} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); // check for incompatible mods @@ -55,7 +57,7 @@ public void OnSettingsUI(UIHelperBase helper) { private static void CheckForIncompatibleMods() { if (GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { ModsCompatibilityChecker mcc = new ModsCompatibilityChecker(); - mcc.PerformModCheck(); + mcc.PerformModCheck(Build); } } } diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 20e93dfe2..5dd5c9284 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -36,11 +36,13 @@ public ModsCompatibilityChecker() /// /// Initiates scan for incompatible mods. If any found, and the user has enabled the mod checker, it creates and initialises the modal dialog panel. /// - public void PerformModCheck() + /// + /// Used to filter out current mod when checking for offline TM:PE installs. + public void PerformModCheck(int build) { try { - Dictionary detected = ScanForIncompatibleMods(); + Dictionary detected = ScanForIncompatibleMods(build); if (detected.Count > 0 && State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { @@ -53,6 +55,7 @@ public void PerformModCheck() } catch (Exception e) { + Log.Info("Something went wrong while checking incompatible mods - see main game log for details."); Debug.LogException(e); } } @@ -60,12 +63,14 @@ public void PerformModCheck() /// /// Iterates installed mods looking for known incompatibilities. /// + /// + /// Used to filter out current mod when checking for offline TM:PE installs. /// /// A list of detected incompatible mods. /// /// Invalid folder path (contains invalid characters, is empty, or contains only white spaces). /// Path is too long (longer than the system-defined maximum length). - public Dictionary ScanForIncompatibleMods() + public Dictionary ScanForIncompatibleMods(int build) { Log.Info("Scanning for incompatible mods"); @@ -93,11 +98,10 @@ public Dictionary ScanForIncompatibleMods() } #if !DEBUG // Workshop TM:PE builds treat local builds as incompatible - else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) + else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) && build != GetModBuild(mod)) { Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); string folder = Path.GetFileName(mod.modPath); - //string folder = mod.modPath.Split(Path.DirectorySeparatorChar).Last(); results.Add(mod, $"{modName} in /{folder}"); } #endif @@ -123,6 +127,27 @@ public string GetModName(PluginInfo plugin) return ((IUserMod)plugin.userModInstance).Name; } + /// + /// Gets the build number of an offline TM:PE mod + /// + /// It will return the if found, otherwise 0. + /// + /// + /// The associated with the mod. + /// + /// The name of the specified plugin. + public int GetModBuild(PluginInfo plugin) + { + try + { + return ((TrafficManagerMod)plugin.userModInstance).Build; + } + catch (Exception) + { + return 0; + } + } + /// /// Works out if the game is effectively running in offline mode, in which no workshop mod subscriptions will be active. /// From b8ec7fa5c71456e45548b60770f1b4329f54336b Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 1 Jul 2019 22:13:29 +0100 Subject: [PATCH 114/142] Use `ModuleVersionId` Guid instead of build --- TLM/TLM/TrafficManagerMod.cs | 4 +--- TLM/TLM/Util/ModsCompatibilityChecker.cs | 27 ++++++++---------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 24f4969d2..5efb07db0 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -17,8 +17,6 @@ public class TrafficManagerMod : IUserMod { public static readonly uint GameVersionC = 0u; public static readonly uint GameVersionBuild = 5u; - public static int Build => Assembly.GetExecutingAssembly().GetName().Version.Build; - // Note: `Version` is also used in UI/MainMenu/VersionLabel.cs public static readonly string Version = "10.20"; @@ -57,7 +55,7 @@ public void OnSettingsUI(UIHelperBase helper) { private static void CheckForIncompatibleMods() { if (GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { ModsCompatibilityChecker mcc = new ModsCompatibilityChecker(); - mcc.PerformModCheck(Build); + mcc.PerformModCheck(); } } } diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 5dd5c9284..f93752f65 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -36,13 +36,11 @@ public ModsCompatibilityChecker() /// /// Initiates scan for incompatible mods. If any found, and the user has enabled the mod checker, it creates and initialises the modal dialog panel. /// - /// - /// Used to filter out current mod when checking for offline TM:PE installs. - public void PerformModCheck(int build) + public void PerformModCheck() { try { - Dictionary detected = ScanForIncompatibleMods(build); + Dictionary detected = ScanForIncompatibleMods(); if (detected.Count > 0 && State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { @@ -64,15 +62,15 @@ public void PerformModCheck(int build) /// Iterates installed mods looking for known incompatibilities. /// /// - /// Used to filter out current mod when checking for offline TM:PE installs. - /// /// A list of detected incompatible mods. /// /// Invalid folder path (contains invalid characters, is empty, or contains only white spaces). /// Path is too long (longer than the system-defined maximum length). - public Dictionary ScanForIncompatibleMods(int build) + public Dictionary ScanForIncompatibleMods() { - Log.Info("Scanning for incompatible mods"); + Guid selfModVerId = Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId; + + Log.Info($"Scanning for incompatible mods; GUID = {selfModVerId}"); // list of installed incompatible mods Dictionary results = new Dictionary(); @@ -98,7 +96,7 @@ public Dictionary ScanForIncompatibleMods(int build) } #if !DEBUG // Workshop TM:PE builds treat local builds as incompatible - else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) && build != GetModBuild(mod)) + else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) && GetModBuild(mod) != selfModVerId) { Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); string folder = Path.GetFileName(mod.modPath); @@ -136,16 +134,9 @@ public string GetModName(PluginInfo plugin) /// The associated with the mod. /// /// The name of the specified plugin. - public int GetModBuild(PluginInfo plugin) + public Guid GetModVerId(PluginInfo plugin) { - try - { - return ((TrafficManagerMod)plugin.userModInstance).Build; - } - catch (Exception) - { - return 0; - } + return plugin.userModInstance.GetType().Assembly.ManifestModule.ModuleVersionId; } /// From c92d05b846ef77677bee882bc50946ef582a12f4 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 1 Jul 2019 22:16:21 +0100 Subject: [PATCH 115/142] Fixed method name typo --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index f93752f65..76ef86881 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -96,7 +96,7 @@ public Dictionary ScanForIncompatibleMods() } #if !DEBUG // Workshop TM:PE builds treat local builds as incompatible - else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) && GetModBuild(mod) != selfModVerId) + else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) && GetModVerId(mod) != selfModVerId) { Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); string folder = Path.GetFileName(mod.modPath); From c50f096de9f382cb2d8e99647b4c95c68e459006 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 1 Jul 2019 22:25:46 +0100 Subject: [PATCH 116/142] Improved logging --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 76ef86881..9dcd6139c 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -70,7 +70,7 @@ public Dictionary ScanForIncompatibleMods() { Guid selfModVerId = Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId; - Log.Info($"Scanning for incompatible mods; GUID = {selfModVerId}"); + Log.Info($"Scanning for incompatible mods; Self GUID = {selfModVerId}"); // list of installed incompatible mods Dictionary results = new Dictionary(); @@ -96,11 +96,18 @@ public Dictionary ScanForIncompatibleMods() } #if !DEBUG // Workshop TM:PE builds treat local builds as incompatible - else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) && GetModVerId(mod) != selfModVerId) + else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) { - Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); - string folder = Path.GetFileName(mod.modPath); - results.Add(mod, $"{modName} in /{folder}"); + if (GetModVerId(mod) == selfModVerId) + { + Log.Info($"Skipping local TM:PE with same GUID as self: {selfModVerId}"); + } + else + { + Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); + string folder = Path.GetFileName(mod.modPath); + results.Add(mod, $"{modName} in /{folder}"); + } } #endif } From e976397b85c3eb28117fbf03a2ee1984c226cdb6 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 1 Jul 2019 22:36:20 +0100 Subject: [PATCH 117/142] Include PR #400 --- CHANGELOG.md | 6 +++--- README.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e518fb0..bf2ec911a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ - Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) - Added: Keybinds tab in mod options - choose your own shortcuts! (thanks kvakvs) (#382) - Added: Show keyboard shortcuts in button tooltips where applicable (thanks kvakvs) (#382) -- Added: Basic support of offline mode for users playing on EA's Origin service (#333) -- Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) +- Added: Basic support of offline mode for users playing on EA's Origin service (#333, #400) +- Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211, #400) - Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid) (#362) - Fixed: Mail trucks ignoring lane arrows (thanks Subaru & eudyptula for feedback) (#307, #338) - Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) @@ -21,7 +21,7 @@ - Fixed: Minor issues regarding saving/loading junction restrictions (#358) - Fixed: Changes of default junction restrictions not reflected in UI overlay (#358) - Fixed: Resetting stuck cims unpauses the simulation (#358, #351) -- Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190) +- Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190, #400) - Fixed: TargetInvocationException in mod compatibility checker (#386, #333) - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) diff --git a/README.md b/README.md index 6c7598281..032107f0e 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ - Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) - Added: Keybinds tab in mod options - choose your own shortcuts! (thanks kvakvs) (#382) - Added: Show keyboard shortcuts in button tooltips where applicable (thanks kvakvs) (#382) -- Added: Basic support of offline mode for users playing on EA's Origin service (#333) -- Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211) +- Added: Basic support of offline mode for users playing on EA's Origin service (#333, #400) +- Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211, #400) - Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid) (#362) - Fixed: Mail trucks ignoring lane arrows (thanks Subaru & eudyptula for feedback) (#307, #338) - Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) @@ -27,7 +27,7 @@ - Fixed: Minor issues regarding saving/loading junction restrictions (#358) - Fixed: Changes of default junction restrictions not reflected in UI overlay (#358) - Fixed: Resetting stuck cims unpauses the simulation (#358, #351) -- Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190) +- Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190, #400) - Fixed: TargetInvocationException in mod compatibility checker (#386, #333) - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) From 095cd0f593c16cfc50046d6a89ec096f3bb59822 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 1 Jul 2019 22:46:30 +0100 Subject: [PATCH 118/142] Include mod path in logging of skipped local builds --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 9dcd6139c..62a4899da 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -100,7 +100,7 @@ public Dictionary ScanForIncompatibleMods() { if (GetModVerId(mod) == selfModVerId) { - Log.Info($"Skipping local TM:PE with same GUID as self: {selfModVerId}"); + Log.Info($"Skipping local TM:PE with GUID '{selfModVerId}' in '{mod.modPath}'"); } else { From fc90187a58f4c3b0a38040c309534f3f6eb63e91 Mon Sep 17 00:00:00 2001 From: krzychu124 Date: Mon, 1 Jul 2019 23:55:01 +0200 Subject: [PATCH 119/142] GameVersion numbers update --- TLM/TLM/TrafficManagerMod.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 446030b25..e2c8ee9e0 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -11,11 +11,11 @@ namespace TrafficManager { public class TrafficManagerMod : IUserMod { - public static readonly uint GameVersion = 184673552u; + public static readonly uint GameVersion = 184803856u; public static readonly uint GameVersionA = 1u; public static readonly uint GameVersionB = 12u; - public static readonly uint GameVersionC = 0u; - public static readonly uint GameVersionBuild = 5u; + public static readonly uint GameVersionC = 1u; + public static readonly uint GameVersionBuild = 2u; // Note: `Version` is also used in UI/MainMenu/VersionLabel.cs public static readonly string Version = "11.0-alpha"; From a9a03aa4a255a1640b30a1d925266d52ce29a9eb Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 00:30:11 +0100 Subject: [PATCH 120/142] Detect _any_ other version of TM:PE, regardless of online vs. offline Updated logging, TM:PE detection, also output GUID when mod enables --- TLM/TLM/TrafficManagerMod.cs | 4 +- TLM/TLM/Util/ModsCompatibilityChecker.cs | 89 +++++------------------- 2 files changed, 20 insertions(+), 73 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 5efb07db0..d72420d03 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -1,12 +1,9 @@ using CSUtil.Commons; using ICities; using System.Reflection; -using System.Runtime.CompilerServices; -using ColossalFramework; using ColossalFramework.UI; using TrafficManager.State; using TrafficManager.Util; -using UnityEngine; namespace TrafficManager { public class TrafficManagerMod : IUserMod { @@ -34,6 +31,7 @@ public class TrafficManagerMod : IUserMod { public void OnEnabled() { Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} {Branch} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); + Log.Info($"Enabled TM:PE has GUID {Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId}"); // check for incompatible mods if (UIView.GetAView() != null) { // when TM:PE is enabled in content manager diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 62a4899da..70b1b44d4 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -1,7 +1,4 @@ using ColossalFramework; -#if !DEBUG -using ColossalFramework.PlatformServices; // used in RELEASE builds -#endif using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; @@ -18,7 +15,6 @@ namespace TrafficManager.Util { public class ModsCompatibilityChecker { - public const ulong LOCAL_MOD = ulong.MaxValue; // Used for LoadIncompatibleModsList() @@ -68,9 +64,9 @@ public void PerformModCheck() /// Path is too long (longer than the system-defined maximum length). public Dictionary ScanForIncompatibleMods() { - Guid selfModVerId = Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId; + Guid selfGuid = Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId; - Log.Info($"Scanning for incompatible mods; Self GUID = {selfModVerId}"); + Log.Info($"Scanning for incompatible mods; My GUID = {selfGuid}"); // list of installed incompatible mods Dictionary results = new Dictionary(); @@ -78,38 +74,36 @@ public Dictionary ScanForIncompatibleMods() // only check enabled mods? bool filterToEnabled = State.GlobalConfig.Instance.Main.IgnoreDisabledMods; -#if !DEBUG - bool offline = IsOffline(); -#endif - // iterate plugins foreach (PluginInfo mod in Singleton.instance.GetPluginsInfo()) { if (!mod.isBuiltin && !mod.isCameraScript && (!filterToEnabled || mod.isEnabled)) { string modName = GetModName(mod); + ulong workshopID = mod.publishedFileID.AsUInt64; - if (incompatibleMods.ContainsKey(mod.publishedFileID.AsUInt64)) + if (incompatibleMods.ContainsKey(workshopID)) { - Log.Info($"Incompatible mod: {mod.publishedFileID.AsUInt64} - {modName}"); + // must be online workshop mod + Log.Info($"Incompatible with: {workshopID} - {modName}"); results.Add(mod, modName); } -#if !DEBUG - // Workshop TM:PE builds treat local builds as incompatible - else if (!offline && mod.publishedFileID.AsUInt64 == LOCAL_MOD && (modName.Contains("TM:PE") || modName.Contains("Traffic Manager"))) + else if (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) { - if (GetModVerId(mod) == selfModVerId) + // It's a TM:PE build - either local or workshop + string workshopIDstr = workshopID == LOCAL_MOD ? "LOCAL" : workshopID.ToString(); + Guid currentGuid = GetModGuid(mod); + + if (currentGuid == selfGuid) { - Log.Info($"Skipping local TM:PE with GUID '{selfModVerId}' in '{mod.modPath}'"); + Log.Info($"Found myself: '{modName}' (Workshop ID: {workshopIDstr}, GUID: {currentGuid}) in '{mod.modPath}'"); } else { - Log.Info($"Local TM:PE detected: '{modName}' in '{mod.modPath}'"); - string folder = Path.GetFileName(mod.modPath); - results.Add(mod, $"{modName} in /{folder}"); + Log.Info($"Detected conflicting '{modName}' (Workshop ID: {workshopIDstr}, GUID: {currentGuid}) in '{mod.modPath}'"); + results.Add(mod, $"{modName} in /{Path.GetFileName(mod.modPath)}"); } } -#endif } } @@ -133,52 +127,17 @@ public string GetModName(PluginInfo plugin) } /// - /// Gets the build number of an offline TM:PE mod - /// - /// It will return the if found, otherwise 0. + /// Gets the of a mod. /// /// /// The associated with the mod. /// - /// The name of the specified plugin. - public Guid GetModVerId(PluginInfo plugin) + /// The of the mod. + public Guid GetModGuid(PluginInfo plugin) { return plugin.userModInstance.GetType().Assembly.ManifestModule.ModuleVersionId; } - /// - /// Works out if the game is effectively running in offline mode, in which no workshop mod subscriptions will be active. - /// - /// Applicalbe "offline" states include: - /// - /// * Origin plaform service (no support for Steam workshop) - /// * Steam (or other platform service) not active - /// * --noWorkshop launch option - /// - /// This is allows LABS and STABLE builds to be used offline without trying to delete themselves. - /// - /// - /// Returns true if game is offline for any reason, otherwise false. -#if !DEBUG - private bool IsOffline() - { - // TODO: Work out if TGP and QQGame platform services allow workshop - if (PluginManager.noWorkshop) - { - return true; - } - else if (PlatformService.platformType == PlatformType.Origin) - { - return true; - } - else if (!PlatformService.active) - { - return true; - } - return false; - } -#endif - /// /// Loads and parses the incompatible_mods.txt resource, adds other workshop branches of TM:PE as applicable. /// @@ -199,7 +158,7 @@ private Dictionary LoadListOfIncompatibleMods() } } - Log.Info($"{INCOMPATIBLE_MODS_FILE} contains {lines.Length} entries"); + Log.Info($"{RESOURCES_PREFIX}{INCOMPATIBLE_MODS_FILE} contains {lines.Length} entries"); // parse the file for (int i = 0; i < lines.Length; i++) @@ -212,16 +171,6 @@ private Dictionary LoadListOfIncompatibleMods() } } - // Treat other workshop-published branches of TM:PE, as applicable, as conflicts -#if LABS - results.Add(583429740u, "TM:PE STABLE"); -#elif DEBUG - results.Add(1637663252u, "TM:PE LABS"); - results.Add(583429740u, "TM:PE STABLE"); -#else - results.Add(1637663252u, "TM:PE LABS"); -#endif - return results; } } From 739e62d680ab6381ad4b9d57b456e7df6650b910 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Tue, 2 Jul 2019 03:14:08 +0200 Subject: [PATCH 121/142] Modal handling fixed; Key conflict finding fixed --- TLM/TLM/State/Keybinds/KeybindSetting.cs | 2 +- TLM/TLM/State/Keybinds/KeybindSettingsBase.cs | 29 ++- TLM/TLM/State/Keybinds/KeybindUI.cs | 172 +++++++++--------- 3 files changed, 106 insertions(+), 97 deletions(-) diff --git a/TLM/TLM/State/Keybinds/KeybindSetting.cs b/TLM/TLM/State/Keybinds/KeybindSetting.cs index 226db9410..bc28cc4ea 100644 --- a/TLM/TLM/State/Keybinds/KeybindSetting.cs +++ b/TLM/TLM/State/Keybinds/KeybindSetting.cs @@ -52,7 +52,7 @@ public void OnKeyChanged(OnKeyChangedHandler onChanged) { } public void NotifyKeyChanged() { - onKeyChanged_(); + onKeyChanged_?.Invoke(); } public KeybindSetting(string cat, diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs index 1944448f2..cfc27661c 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs @@ -9,51 +9,50 @@ namespace TrafficManager.State.Keybinds { using UnityEngine; public class KeybindSettingsBase : UICustomControl { - private static void OnMainMenuShortcutChanged(SavedInputKey savedinputkey) { - LoadingExtension.BaseUI.MainMenuButton.UpdateTooltip(); - Log.Info("Main menu shortcut changed"); - } - protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; public const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keybinds"; + // NOTE: Do not change fields to properties, otherwise also fix the conflict + // detection code in KeybindUI.FindConflict(inTmpe) which expects these below + // to be fields of type KeybindSetting. + /// /// This input key can not be changed and is not checked, instead it is display only /// - protected static KeybindSetting ToolCancelViewOnly { get; } = new KeybindSetting( + protected static KeybindSetting ToolCancelViewOnly = new KeybindSetting( "Global", "Key_ExitSubtool", SavedInputKey.Encode(KeyCode.Escape, false, false, false)); - public static KeybindSetting ToggleMainMenu { get; } = new KeybindSetting( + public static KeybindSetting ToggleMainMenu = new KeybindSetting( "Global", "Key_ToggleTMPEMainMenu", SavedInputKey.Encode(KeyCode.Semicolon, false, true, false)); - public static KeybindSetting ToggleTrafficLightTool { get; } = + public static KeybindSetting ToggleTrafficLightTool = new KeybindSetting("Global", "Key_ToggleTrafficLightTool"); - public static KeybindSetting LaneArrowTool { get; } = + public static KeybindSetting LaneArrowTool = new KeybindSetting("Global", "Key_LaneArrowTool"); - public static KeybindSetting LaneConnectionsTool { get; } = + public static KeybindSetting LaneConnectionsTool = new KeybindSetting("Global", "Key_LaneConnectionsTool"); - public static KeybindSetting PrioritySignsTool { get; } = + public static KeybindSetting PrioritySignsTool = new KeybindSetting("Global", "Key_PrioritySignsTool"); - public static KeybindSetting JunctionRestrictionsTool { get; } = + public static KeybindSetting JunctionRestrictionsTool = new KeybindSetting("Global", "Key_JunctionRestrictionsTool"); - public static KeybindSetting SpeedLimitsTool { get; } = + public static KeybindSetting SpeedLimitsTool = new KeybindSetting("Global", "Key_SpeedLimitsTool"); - public static KeybindSetting LaneConnectorStayInLane { get; } = new KeybindSetting( + public static KeybindSetting LaneConnectorStayInLane = new KeybindSetting( "LaneConnector", "Key_LaneConnector_StayInLane", SavedInputKey.Encode(KeyCode.S, false, true, false)); - public static KeybindSetting LaneConnectorDelete { get; } = new KeybindSetting( + public static KeybindSetting LaneConnectorDelete = new KeybindSetting( "LaneConnector", "Key_LaneConnector_Delete", SavedInputKey.Encode(KeyCode.Delete, false, false, false), diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index 0427a5d2b..8945860dd 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -1,4 +1,5 @@ using System; +using CSUtil.Commons; namespace TrafficManager.State.Keybinds { using System.Linq; @@ -189,102 +190,108 @@ public void AddGroup(string title, Action code) { EndGroup(); } - private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter p) { - // This will only work if the user clicked the modify button - // otherwise no effect - if (!currentlyEditedBinding_.HasValue || Keybind.IsModifierKey(p.keycode)) { - return; - } - - p.Use(); // Consume the event - - var editedBinding = currentlyEditedBinding_; // will be nulled by popmodal - UIView.PopModal(); - currentlyEditedBinding_ = editedBinding; + private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter evParam) { + try { + // This will only work if the user clicked the modify button + // otherwise no effect + if (!currentlyEditedBinding_.HasValue || Keybind.IsModifierKey(evParam.keycode)) { + return; + } - var keybindButton = p.source as UIButton; + evParam.Use(); // Consume the event + var editedBinding = currentlyEditedBinding_; // will be nulled by closing modal + UIView.PopModal(); - if (p.keycode != KeyCode.Escape) { - var inputKey = SavedInputKey.Encode(p.keycode, p.control, p.shift, p.alt); - var editable = (KeybindSetting.Editable) p.source.objectUserData; + var keybindButton = evParam.source as UIButton; + var inputKey = SavedInputKey.Encode(evParam.keycode, evParam.control, evParam.shift, evParam.alt); + var editable = (KeybindSetting.Editable) evParam.source.objectUserData; var category = editable.Target.Category; - var maybeConflict = FindConflict(inputKey, category); - if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); - } else { - currentlyEditedBinding_.Value.TargetKey.value = inputKey; - currentlyEditedBinding_.Value.Target.NotifyKeyChanged(); + if (evParam.keycode != KeyCode.Escape) { + // Check the key conflict + var maybeConflict = FindConflict(editedBinding.Value, inputKey, category); + if (maybeConflict != string.Empty) { + var message = Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict; + Log.Info($"Keybind conflict: {message}"); + UIView.library + .ShowModal("ExceptionPanel") + .SetMessage("Key Conflict", message, false); + } else { + editedBinding.Value.TargetKey.value = inputKey; + editedBinding.Value.Target.NotifyKeyChanged(); + } } - } - // Update text on the button - EndButtonEditMode(keybindButton); + keybindButton.text = Keybind.Str(editedBinding.Value.TargetKey); + currentlyEditedBinding_ = null; + } catch (Exception e) {Log.Error($"{e}");} } - private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter p) { - var editable = (KeybindSetting.Editable) p.source.objectUserData; - var keybindButton = p.source as UIButton; + private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter evParam) { + var editable = (KeybindSetting.Editable) evParam.source.objectUserData; + var keybindButton = evParam.source as UIButton; // This will only work if the user is not in the process of changing the shortcut if (currentlyEditedBinding_ == null) { - p.Use(); - currentlyEditedBinding_ = editable; - - keybindButton.buttonsMask = - UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | - UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | - UIMouseButton.Special3; - keybindButton.text = "Press any key"; - keybindButton.Focus(); - UIView.PushModal(keybindButton, OnKeybindModalPopped); - } else if (!Keybind.IsUnbindableMouseButton(p.buttons)) { + evParam.Use(); + StartKeybindEditMode(editable, keybindButton); + } else if (!Keybind.IsUnbindableMouseButton(evParam.buttons)) { // This will work if the user clicks while the shortcut change is in progress - p.Use(); - - var editedBinding = currentlyEditedBinding_; // it will be nulled on modal pop + evParam.Use(); + var editedBinding = currentlyEditedBinding_; // will be nulled by closing modal UIView.PopModal(); - currentlyEditedBinding_ = editedBinding; - var inputKey = SavedInputKey.Encode(Keybind.ButtonToKeycode(p.buttons), + var inputKey = SavedInputKey.Encode(Keybind.ButtonToKeycode(evParam.buttons), Keybind.IsControlDown(), Keybind.IsShiftDown(), Keybind.IsAltDown()); var category = editable.Target.Category; - var maybeConflict = FindConflict(inputKey, category); + var maybeConflict = FindConflict(editedBinding.Value, inputKey, category); if (maybeConflict != string.Empty) { - UIView.library.ShowModal("ExceptionPanel").SetMessage( - "Key Conflict", - Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict, - false); + var message = Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict; + Log.Info($"Keybind conflict: {message}"); + UIView.library + .ShowModal("ExceptionPanel") + .SetMessage("Key Conflict", message, false); } else { - currentlyEditedBinding_.Value.TargetKey.value = inputKey; - currentlyEditedBinding_.Value.Target.NotifyKeyChanged(); + editedBinding.Value.TargetKey.value = inputKey; + editedBinding.Value.Target.NotifyKeyChanged(); } keybindButton.buttonsMask = UIMouseButton.Left; - EndButtonEditMode(keybindButton); + keybindButton.text = Keybind.Str(editedBinding.Value.TargetKey); + currentlyEditedBinding_ = null; } } + /// + /// Set the button text to welcoming message. Push the button as modal blocking + /// everything else on screen and capturing the input. + /// + /// The keysetting and inputkey inside it, to edit + /// The button to become modal + private void StartKeybindEditMode(KeybindSetting.Editable editable, UIButton keybindButton) { + currentlyEditedBinding_ = editable; + + keybindButton.buttonsMask = + UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | + UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | + UIMouseButton.Special3; + keybindButton.text = "Press key (or Esc)"; + keybindButton.Focus(); + UIView.PushModal(keybindButton, OnKeybindModalPopped); + } + /// /// Called by the UIView when modal was popped without us knowing /// /// The button which temporarily was modal private void OnKeybindModalPopped(UIComponent component) { - if (!(component is UIButton) || currentlyEditedBinding_ == null) { - return; + var keybindButton = component as UIButton; + if (keybindButton != null && currentlyEditedBinding_ != null) { + keybindButton.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + currentlyEditedBinding_ = null; } - - EndButtonEditMode((UIButton) component); - } - - private void EndButtonEditMode(UIButton b) { - b.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); - currentlyEditedBinding_ = null; } /// @@ -294,7 +301,9 @@ private void EndButtonEditMode(UIButton b) { /// Key to search for the conflicts /// Check the same category keys if possible /// Empty string for no conflict, or the conflicting key name - private string FindConflict(InputKey sample, string sampleCategory) { + private string FindConflict(KeybindSetting.Editable editedKeybind, + InputKey sample, + string sampleCategory) { if (Keybind.IsEmpty(sample)) { // empty key never conflicts return string.Empty; @@ -306,15 +315,15 @@ private string FindConflict(InputKey sample, string sampleCategory) { } // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. - var saveEditingBinding = currentlyEditedBinding_.Value.TargetKey.value; - currentlyEditedBinding_.Value.TargetKey.value = SavedInputKey.Empty; + var saveEditingBinding = editedKeybind.TargetKey.value; + editedKeybind.TargetKey.value = SavedInputKey.Empty; // Check in TMPE settings var tmpeSettingsType = typeof(KeybindSettingsBase); var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); - currentlyEditedBinding_.Value.TargetKey.value = saveEditingBinding; + editedKeybind.TargetKey.value = saveEditingBinding; return inTmpe; } @@ -365,11 +374,13 @@ private static InputKey GetDefaultEntryInGameSettings(string entryName) { /// and the same category if it is not Global. This will allow reusing key in other tool /// categories without conflicting. /// - /// The key to search for - /// The category Global or some tool name + /// The key to search for + /// The category Global or some tool name /// Fields of the key settings class /// Empty string if no conflicts otherwise the key name to print an error - private static string FindConflictInTmpe(InputKey sample, string sampleCategory, FieldInfo[] fields) { + private static string FindConflictInTmpe(InputKey testSample, + string testSampleCategory, + FieldInfo[] fields) { foreach (var field in fields) { // This will match inputkeys of TMPE key settings if (field.FieldType != typeof(KeybindSetting)) { @@ -378,17 +389,16 @@ private static string FindConflictInTmpe(InputKey sample, string sampleCategory, var tmpeSetting = field.GetValue(null) as KeybindSetting; - // Check category, category=Global will check keys in all categories + // Check category + // settingCategory=Global will check against any other test samples // category= will check Global and its own only - if (sampleCategory != "Global" - && sampleCategory != tmpeSetting.Category) { - continue; - } - - if (tmpeSetting.HasKey(sample)) { - return "TM:PE, " - + Translation.GetString("Keybind_category_" + tmpeSetting.Category) - + " -- " + CamelCaseSplit(field.Name); + if (tmpeSetting.Category == "Global" + || testSampleCategory == tmpeSetting.Category) { + if (tmpeSetting.HasKey(testSample)) { + return "TM:PE, " + + Translation.GetString("Keybind_category_" + tmpeSetting.Category) + + " -- " + CamelCaseSplit(field.Name); + } } } From 49db07cc30f84203884df1775151b6856a71788a Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 03:44:56 +0100 Subject: [PATCH 122/142] Minor code cleanup --- TLM/TLM/Util/ModsCompatibilityChecker.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index 70b1b44d4..6389e8cbf 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -22,11 +22,11 @@ public class ModsCompatibilityChecker private const string INCOMPATIBLE_MODS_FILE = "incompatible_mods.txt"; // parsed contents of incompatible_mods.txt - private readonly Dictionary incompatibleMods; + private readonly Dictionary knownIncompatibleMods; public ModsCompatibilityChecker() { - incompatibleMods = LoadListOfIncompatibleMods(); + knownIncompatibleMods = LoadListOfIncompatibleMods(); } /// @@ -38,7 +38,7 @@ public void PerformModCheck() { Dictionary detected = ScanForIncompatibleMods(); - if (detected.Count > 0 && State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) + if (detected.Count > 0) { IncompatibleModsPanel panel = UIView.GetAView().AddUIComponent(typeof(IncompatibleModsPanel)) as IncompatibleModsPanel; panel.IncompatibleMods = detected; @@ -82,7 +82,7 @@ public Dictionary ScanForIncompatibleMods() string modName = GetModName(mod); ulong workshopID = mod.publishedFileID.AsUInt64; - if (incompatibleMods.ContainsKey(workshopID)) + if (knownIncompatibleMods.ContainsKey(workshopID)) { // must be online workshop mod Log.Info($"Incompatible with: {workshopID} - {modName}"); From eff32b2f196d0639a17b6b917223f82975a05c4e Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Tue, 2 Jul 2019 19:56:24 +0200 Subject: [PATCH 123/142] Review notes; Renamings; PL translation update --- TLM/TLM/Resources/lang_pl.txt | 8 ++++---- TLM/TLM/State/Keybinds/Keybind.cs | 5 ++--- TLM/TLM/State/Keybinds/KeybindSetting.cs | 6 +++--- TLM/TLM/State/Keybinds/KeybindUI.cs | 12 ++++++------ TLM/TLM/UI/LinearSpriteButton.cs | 2 +- TLM/TLM/UI/UIMainMenuButton.cs | 2 +- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 7e76c522d..f7eea10d4 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -241,12 +241,12 @@ Keybind_use_priority_signs_tool Narzędzie 'Znaki pierwszeństwa' Keybind_use_junction_restrictions_tool Narzędzie 'Ograniczenia na skrzyżowaniu' Keybind_use_speed_limits_tool Narzędzie 'Limity prędkości' Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie -Keybind_lane_connector_delete Oczyść połączenia pasów ruchu +Keybind_lane_connector_delete Usuń połączenia pasów ruchu Keybinds Skróty klawiszowe Keybind_Exit_subtool Wyłącz narzędzie i zamknij TM:PE menu -Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. -Keybind_category_Global Global keys -Keybind_category_LaneConnector Lane Connector Tool keys +Keybind_conflict Wybrany skrót klawiszowy jest już używany. Wybierz inną kombinację. +Keybind_category_Global Główne +Keybind_category_LaneConnector Narzędzie: Połącz pasy ruchu Display_speed_limits_mph Wyświetlaj limity prędkości w mph zamiast km/h Miles_per_hour Mile na godzinę Kilometers_per_hour Kilometry na godzinę diff --git a/TLM/TLM/State/Keybinds/Keybind.cs b/TLM/TLM/State/Keybinds/Keybind.cs index 0623d0dd4..827b8735c 100644 --- a/TLM/TLM/State/Keybinds/Keybind.cs +++ b/TLM/TLM/State/Keybinds/Keybind.cs @@ -9,8 +9,7 @@ namespace TrafficManager.State.Keybinds { public class Keybind { public static bool IsEmpty(InputKey sample) { var noKey = SavedInputKey.Encode(KeyCode.None, false, false, false); - return sample == SavedInputKey.Empty - || sample == noKey; + return sample == SavedInputKey.Empty || sample == noKey; } /// @@ -18,7 +17,7 @@ public static bool IsEmpty(InputKey sample) { /// /// The key /// The shortcut, example: "Ctrl + Alt + H" - public static string Str(SavedInputKey k) { + public static string ToLocalizedString(SavedInputKey k) { return k.ToLocalizedString("KEYNAME"); } diff --git a/TLM/TLM/State/Keybinds/KeybindSetting.cs b/TLM/TLM/State/Keybinds/KeybindSetting.cs index bc28cc4ea..abbce3c16 100644 --- a/TLM/TLM/State/Keybinds/KeybindSetting.cs +++ b/TLM/TLM/State/Keybinds/KeybindSetting.cs @@ -77,10 +77,10 @@ public KeybindSetting(string cat, /// /// Prefix will be added if any key is not empty /// String tooltip with the key shortcut or two - public string Str(string prefix = "") { + public string ToLocalizedString(string prefix = "") { var result = default(string); if (!Keybind.IsEmpty(Key)) { - result += prefix + Keybind.Str(Key); + result += prefix + Keybind.ToLocalizedString(Key); } if (AlternateKey == null || Keybind.IsEmpty(AlternateKey)) { @@ -93,7 +93,7 @@ public string Str(string prefix = "") { result += " | "; } - return result + Keybind.Str(AlternateKey); + return result + Keybind.ToLocalizedString(AlternateKey); } public bool IsPressed(Event e) { diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index 8945860dd..e0937efd5 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -132,7 +132,7 @@ public void CreateKeybindButton(UIPanel parent, KeybindSetting setting, SavedInp float widthFraction) { var btn = parent.AddUIComponent(); btn.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); - btn.text = Keybind.Str(editKey); + btn.text = Keybind.ToLocalizedString(editKey); btn.hoveredTextColor = new Color32(128, 128, 255, 255); // darker blue btn.pressedTextColor = new Color32(192, 192, 255, 255); // lighter blue btn.normalBgSprite = "ButtonMenu"; @@ -160,7 +160,7 @@ private static void AddXButton(UIPanel parent, SavedInputKey editKey, UIButton a btnX.pressedBgSprite = "buttonclosepressed"; btnX.eventClicked += (component, eventParam) => { editKey.value = SavedInputKey.Empty; - alignTo.text = Keybind.Str(editKey); + alignTo.text = Keybind.ToLocalizedString(editKey); }; } @@ -173,7 +173,7 @@ public void CreateKeybindText(UIPanel parent, SavedInputKey showKey, float width var label = parent.AddUIComponent(); label.autoSize = false; label.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); - label.text = Keybind.Str(showKey); + label.text = Keybind.ToLocalizedString(showKey); label.verticalAlignment = UIVerticalAlignment.Middle; label.textAlignment = UIHorizontalAlignment.Center; label.textColor = new Color32(128, 128, 128, 255); // grey @@ -222,7 +222,7 @@ private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter evParam) { } } - keybindButton.text = Keybind.Str(editedBinding.Value.TargetKey); + keybindButton.text = Keybind.ToLocalizedString(editedBinding.Value.TargetKey); currentlyEditedBinding_ = null; } catch (Exception e) {Log.Error($"{e}");} } @@ -259,7 +259,7 @@ private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter evParam) } keybindButton.buttonsMask = UIMouseButton.Left; - keybindButton.text = Keybind.Str(editedBinding.Value.TargetKey); + keybindButton.text = Keybind.ToLocalizedString(editedBinding.Value.TargetKey); currentlyEditedBinding_ = null; } } @@ -289,7 +289,7 @@ private void StartKeybindEditMode(KeybindSetting.Editable editable, UIButton key private void OnKeybindModalPopped(UIComponent component) { var keybindButton = component as UIButton; if (keybindButton != null && currentlyEditedBinding_ != null) { - keybindButton.text = Keybind.Str(currentlyEditedBinding_.Value.TargetKey); + keybindButton.text = Keybind.ToLocalizedString(currentlyEditedBinding_.Value.TargetKey); currentlyEditedBinding_ = null; } } diff --git a/TLM/TLM/UI/LinearSpriteButton.cs b/TLM/TLM/UI/LinearSpriteButton.cs index f793cb596..fee032556 100644 --- a/TLM/TLM/UI/LinearSpriteButton.cs +++ b/TLM/TLM/UI/LinearSpriteButton.cs @@ -147,7 +147,7 @@ internal void UpdateProperties() { /// /// Tooltip to append to the main tooltip text, or an empty string private string GetShortcutTooltip() { - return ShortcutKey != null ? ShortcutKey.Str("\n") : string.Empty; + return ShortcutKey != null ? ShortcutKey.ToLocalizedString("\n") : string.Empty; } } } \ No newline at end of file diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 303c26ea5..71b9041ca 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -159,7 +159,7 @@ public void OnGUI() { } private string GetTooltip() { - return KeybindSettingsBase.ToggleMainMenu.Str("\n"); + return KeybindSettingsBase.ToggleMainMenu.ToLocalizedString("\n"); } } } \ No newline at end of file From d9e8813b24eb4ca37c05d674679fa0fd110be53e Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Tue, 2 Jul 2019 20:05:28 +0200 Subject: [PATCH 124/142] StyleCop SA1200: Moved usings inside namespace --- TLM/TLM/State/Keybinds/Keybind.cs | 8 ++++---- TLM/TLM/State/Keybinds/KeybindSetting.cs | 5 ++--- TLM/TLM/State/Keybinds/KeybindSettingsPage.cs | 4 ++-- TLM/TLM/State/Keybinds/KeybindUI.cs | 5 ++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/TLM/TLM/State/Keybinds/Keybind.cs b/TLM/TLM/State/Keybinds/Keybind.cs index 827b8735c..dbf494c7c 100644 --- a/TLM/TLM/State/Keybinds/Keybind.cs +++ b/TLM/TLM/State/Keybinds/Keybind.cs @@ -1,8 +1,8 @@ -using ColossalFramework; -using ColossalFramework.UI; -using UnityEngine; - namespace TrafficManager.State.Keybinds { + using ColossalFramework; + using ColossalFramework.UI; + using UnityEngine; + /// /// General input key handling functions, checking for empty, converting to string etc. /// diff --git a/TLM/TLM/State/Keybinds/KeybindSetting.cs b/TLM/TLM/State/Keybinds/KeybindSetting.cs index abbce3c16..e25bbae84 100644 --- a/TLM/TLM/State/Keybinds/KeybindSetting.cs +++ b/TLM/TLM/State/Keybinds/KeybindSetting.cs @@ -1,8 +1,7 @@ -using JetBrains.Annotations; -using UnityEngine; - namespace TrafficManager.State.Keybinds { using ColossalFramework; + using JetBrains.Annotations; + using UnityEngine; /// /// Contains one or two SavedInputKeys, and event handler when the key is changed. diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs index 4a6301bb8..ee3bbeeb0 100644 --- a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -1,6 +1,6 @@ -using TrafficManager.UI; - namespace TrafficManager.State.Keybinds { + using UI; + public class KeybindSettingsPage : KeybindSettingsBase { private void Awake() { TryCreateConfig(); diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index e0937efd5..39a93c1c5 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -1,12 +1,11 @@ -using System; -using CSUtil.Commons; - namespace TrafficManager.State.Keybinds { + using System; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using ColossalFramework; using ColossalFramework.UI; + using CSUtil.Commons; using UI; using UnityEngine; From 308ca3c82795d2c52c9045dd5003aafea3224dbc Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 19:22:51 +0100 Subject: [PATCH 125/142] Update changelogs --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2ec911a..7e80a7d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) - Updated: Polish translation (thanks krzychu124) (#384, #333) -- Updated: Russian translation (thanks vitalii201) (#327, #328) +- Updated: Russian translation (thanks vitalii201 & kvakvs) (#327, #328) - Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) - Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) - Meta: Pathfinder debug logging tools (switch `26`) (#370) diff --git a/README.md b/README.md index 032107f0e..3d341960e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) - Updated: Polish translation (thanks krzychu124) (#384, #333) -- Updated: Russian translation (thanks vitalii201) (#327, #328) +- Updated: Russian translation (thanks vitalii201 & kvakvs) (#327, #328) - Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) - Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) - Meta: Pathfinder debug logging tools (switch `26`) (#370) From 4b3acab5e6a06d5b0755237f6aa4695fb5affa2e Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 19:25:56 +0100 Subject: [PATCH 126/142] Removed #370 from changelog as it's moved to next milestone --- CHANGELOG.md | 1 - README.md | 1 - 2 files changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e80a7d55..785c27e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,6 @@ - Updated: Russian translation (thanks vitalii201 & kvakvs) (#327, #328) - Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) - Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) -- Meta: Pathfinder debug logging tools (switch `26`) (#370) - Meta: Separate binaries for Stable and Labs on GitHub release pages (#360) - Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) - Meta: Added GitHub issue templates for bugs, features, translations. (#272) diff --git a/README.md b/README.md index 3d341960e..55befd9b9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ - Updated: Russian translation (thanks vitalii201 & kvakvs) (#327, #328) - Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) - Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) -- Meta: Pathfinder debug logging tools (switch `26`) (#370) - Meta: Separate binaries for Stable and Labs on GitHub release pages (#360) - Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) - Meta: Added GitHub issue templates for bugs, features, translations. (#272) From 7a7f0a680b2b859fd54f83365e77040017922d06 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Tue, 2 Jul 2019 20:28:38 +0200 Subject: [PATCH 127/142] Use wordWrap=true on UILabels for keybinds for long translations --- TLM/TLM/State/Keybinds/KeybindUI.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs index 39a93c1c5..0b771c76c 100644 --- a/TLM/TLM/State/Keybinds/KeybindUI.cs +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -119,6 +119,7 @@ private void EndGroup() { public UILabel CreateLabel(UIPanel parent, string text, float widthFraction) { var label = parent.AddUIComponent(); + label.wordWrap = true; label.autoSize = false; label.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); label.text = text; From 24961049facf089241ef348750add2e4bb48d8ed Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 19:49:28 +0100 Subject: [PATCH 128/142] Update mod and game version --- TLM/TLM/TrafficManagerMod.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 83d7921ba..fafdfb87f 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -11,14 +11,14 @@ namespace TrafficManager { public class TrafficManagerMod : IUserMod { - public static readonly uint GameVersion = 184673552u; + public static readonly uint GameVersion = 184803856u; public static readonly uint GameVersionA = 1u; public static readonly uint GameVersionB = 12u; - public static readonly uint GameVersionC = 0u; - public static readonly uint GameVersionBuild = 5u; + public static readonly uint GameVersionC = 1u; + public static readonly uint GameVersionBuild = 2u; // Note: `Version` is also used in UI/MainMenu/VersionLabel.cs - public static readonly string Version = "10.20"; + public static readonly string Version = "10.21"; #if LABS public string Branch => "LABS"; From 92e34945af9c43a60aeec5ff8d1025417d72abec Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 20:04:48 +0100 Subject: [PATCH 129/142] Mnetion #403 - game version update - Updated: Game version 1.12.1-f1 compatible (#403) --- CHANGELOG.md | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 785c27e84..21b06e301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Fixed: Resetting stuck cims unpauses the simulation (#358, #351) - Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190, #400) - Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +- Updated: Game version 1.12.1-f1 compatible (#403) - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) - Updated: Polish translation (thanks krzychu124) (#384, #333) diff --git a/README.md b/README.md index 55befd9b9..196e654d2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ - Fixed: Resetting stuck cims unpauses the simulation (#358, #351) - Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190, #400) - Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +- Updated: Game version 1.12.1-f1 compatible (#403) - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) - Updated: Polish translation (thanks krzychu124) (#384, #333) From 29299f68d60767cd6ee496d6c35a3ece8ad6791a Mon Sep 17 00:00:00 2001 From: krzychu124 Date: Tue, 2 Jul 2019 21:38:15 +0200 Subject: [PATCH 130/142] Blur position and zOrder fix --- TLM/TLM/UI/IncompatibleModsPanel.cs | 44 ++++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index bc587dfb3..325536ae8 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -29,7 +29,7 @@ public class IncompatibleModsPanel : UIPanel /// /// Initialises the dialog, populates it with list of incompatible mods, and adds it to the modal stack. - /// + /// /// If the modal stack was previously empty, a blur effect is added over the screen background. /// public void Initialize() @@ -139,20 +139,29 @@ public void Initialize() blurEffect.size = new Vector2(resolution.x, resolution.y); blurEffect.absolutePosition = new Vector3(0, 0); blurEffect.SendToBack(); - if (blurEffect != null) - { - blurEffect.isVisible = true; - ValueAnimator.Animate("ModalEffect", delegate (float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); - } + blurEffect.eventPositionChanged += OnBlurEffectPositionChange; + blurEffect.eventZOrderChanged += OnBlurEffectZOrderChange; + blurEffect.opacity = 0; + blurEffect.isVisible = true; + ValueAnimator.Animate("ModalEffect", delegate (float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); // Make sure modal dialog is in front of all other UI BringToFront(); } + private void OnBlurEffectPositionChange(UIComponent component, Vector2 position) { + blurEffect.absolutePosition = Vector3.zero; + } + + private void OnBlurEffectZOrderChange(UIComponent component, int value) { + blurEffect.zOrder = 0; + mainPanel.zOrder = 1000; + } + /// /// Allows the user to press "Esc" to close the dialog. /// - /// + /// /// Details about the key press. protected override void OnKeyDown(UIKeyEventParameter p) { @@ -170,7 +179,7 @@ protected override void OnKeyDown(UIKeyEventParameter p) /// /// Hnadles click of the "Run incompatible check on startup" checkbox and updates game options accordingly. /// - /// + /// /// The new value of the checkbox; true if checked, otherwise false. private void RunModsCheckerOnStartup_eventCheckChanged(bool value) { @@ -181,7 +190,7 @@ private void RunModsCheckerOnStartup_eventCheckChanged(bool value) /// /// Handles click of the "close dialog" button; pops the dialog off the modal stack. /// - /// + /// /// Handle to the close button UI component (not used). /// Details about the click event. private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) @@ -204,7 +213,7 @@ private void CloseDialog() /// /// Creates a panel representing the mod and adds it to the UI component. /// - /// + /// /// The parent UI component that the panel will be added to. /// The name of the mod, which is displayed to user. /// The instance of the incompatible mod. @@ -226,10 +235,10 @@ private void CreateEntry(ref UIScrollablePanel parent, string modName, PluginInf /// /// Handles click of "Unsubscribe" or "Delete" button; removes the associated mod and updates UI. - /// + /// /// Once all incompatible mods are removed, the dialog will be closed automatically. /// - /// + /// /// A handle to the UI button that was clicked. /// Details of the click event. /// The instance of the mod to remove. @@ -273,9 +282,9 @@ private void UnsubscribeClick(UIComponent component, UIMouseEventParameter event /// /// Deletes a locally installed TM:PE mod. /// - /// + /// /// The associated with the mod that needs deleting. - /// + /// /// Returns true if successfully deleted, otherwise false. private bool DeleteLocalTMPE(PluginInfo mod) { @@ -295,7 +304,7 @@ private bool DeleteLocalTMPE(PluginInfo mod) /// /// Creates an `Unsubscribe` or `Delete` button (as applicable to mod location) and attaches it to the UI component. /// - /// + /// /// The parent UI component which the button will be attached to. /// The translated text to display on the button. /// The x position of the top-left corner of the button, relative to . @@ -334,8 +343,9 @@ private void TryPopModal() } } - if (blurEffect != null && UIView.ModalInputCount() == 0) - { + if (blurEffect != null && UIView.ModalInputCount() == 0) { + blurEffect.eventPositionChanged -= OnBlurEffectPositionChange; + blurEffect.eventZOrderChanged -= OnBlurEffectZOrderChange; ValueAnimator.Animate("ModalEffect", delegate (float val) { blurEffect.opacity = val; }, new AnimatedFloat(1f, 0f, 0.7f, EasingType.CubicEaseOut), delegate () { blurEffect.Hide(); }); } } From c67af20a34b238c6a19d685e4435b75555f67b75 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 20:53:49 +0100 Subject: [PATCH 131/142] Changelog update for #404 and #382 - Blur fix (#404) - `.idea/` added to .gitignore (#382) --- CHANGELOG.md | 3 ++- README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b06e301..fab3bd386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Fixed: Resetting stuck cims unpauses the simulation (#358, #351) - Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190, #400) - Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +- Fixed: Issue with Paradox login blurring compatibility checker dialog (#404) - Updated: Game version 1.12.1-f1 compatible (#403) - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) @@ -34,7 +35,7 @@ - Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) - Meta: Added GitHub issue templates for bugs, features, translations. (#272) - Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) -- Meta: Added entire `.vs/` folder to `.gitignore` (#395) +- Meta: Added entire `.vs/` and `.idea/` folders to `.gitignore` (#395, #382) - Meta: Updated install guide to include section for EA Origin users (#333) - Meta: Enable latest C# `LangVersion` in all projects (#398) diff --git a/README.md b/README.md index 196e654d2..5b21e5da0 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ - Fixed: Resetting stuck cims unpauses the simulation (#358, #351) - Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190, #400) - Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +- Fixed: Issue with Paradox login blurring compatibility checker dialog (#404) - Updated: Game version 1.12.1-f1 compatible (#403) - Updated: Chinese translation (thanks Emphasia) (#375, #336) - Updated: German translation (thanks kvakvs) (#384) @@ -40,7 +41,7 @@ - Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) - Meta: Added GitHub issue templates for bugs, features, translations. (#272) - Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) -- Meta: Added entire `.vs/` folder to `.gitignore` (#395) +- Meta: Added entire `.vs/` and `.idea/` folders to `.gitignore` (#395, #382) - Meta: Updated install guide to include section for EA Origin users (#333) - Meta: Enable latest C# `LangVersion` in all projects (#398) From be496b61893e2fb97f7b0923045866c064a02557 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 2 Jul 2019 21:09:05 +0100 Subject: [PATCH 132/142] Added release date to changelogs --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab3bd386..68017887d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Cities: Skylines - Traffic Manager: *President Edition* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) # Changelog -### [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), ??/07/2019 +### [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), 02/07/2019 - Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) - Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) - Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) diff --git a/README.md b/README.md index 5b21e5da0..886104b25 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ > Having problems with traffic despawning after updating roads or rails? Try [Broken Node Detector](https://steamcommunity.com/sharedfiles/filedetails/?id=1777173984) which helps detect a game bug. Collossal Order are aware of the issue. -#### Version [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), ??/07/2019 +#### Version [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), 02/07/2019 - Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) - Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) - Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) From c607b364a3aa1086dd840f88bf44468e5003c9ba Mon Sep 17 00:00:00 2001 From: Emphasia <574402766@qq.com> Date: Thu, 4 Jul 2019 16:44:05 +0800 Subject: [PATCH 133/142] Update lang_zh.txt update Chinese translation of https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/pull/382 --- TLM/TLM/Resources/lang_zh.txt | 50 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index 5402a29d2..2329fccb7 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -1,4 +1,4 @@ -Switch_traffic_lights 红绿灯增删 +Switch_traffic_lights 红绿灯增删 Add_priority_signs 添加路口优先权 Manual_traffic_lights 手动控制红绿灯 Timed_traffic_lights 红绿灯定时设置 @@ -123,7 +123,7 @@ Default_speed_limit 默认道路限速 Unit_system 单元制度 Metric 公制 Imperial 英制 -Use_more_CPU_cores_for_route_calculation_if_available 使用更多的CPU内核来计算路线(如果可用) +Use_more_CPU_cores_for_route_calculation_if_available 使用更多的CPU核心来计算路线 Activated_features 启用功能 Junction_restrictions 路口管制 Prohibit_spawning_of_pocket_cars 禁止袖珍车产生 @@ -191,7 +191,7 @@ TMPE_TUTORIAL_BODY_JunctionRestrictionsTool 控制车辆和行人在路口的行 TMPE_TUTORIAL_HEAD_LaneArrowTool 变更车道方向 TMPE_TUTORIAL_BODY_LaneArrowTool 限制车辆的可选方向。\n\n1. 单击路口前的路段\n2. 选择允许的方向 TMPE_TUTORIAL_HEAD_LaneConnectorTool 车道连接工具 -TMPE_TUTORIAL_BODY_LaneConnectorTool 连接2+条车道来告知车辆可用的车道。\n\n1. 单击一个车道变更点(灰色圆圈)\n2. 单击一个来源车道\n3. 单击目标车道来建立连接\n4. 任意位置单击右键返回来源车道选择\n\n可用热键:\n\n- Delete or Backspace:移除所有车道连接\n- Shift + S:(循环)查看所有"stay on lane"模式 +TMPE_TUTORIAL_BODY_LaneConnectorTool 连接2+条车道来告知车辆可用的车道。\n\n1. 单击一个车道变更点(灰色圆圈)\n2. 单击一个来源车道\n3. 单击目标车道来建立连接\n4. 任意位置单击右键返回来源车道选择\n\n可用热键:\n\n- Delete 或 Backspace:移除所有车道连接\n- Shift + S:(循环)查看所有"stay on lane"模式 TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool 手动控制红绿灯 TMPE_TUTORIAL_BODY_ManualTrafficLightsTool 尝试自定时的红绿灯。\n\n1. 单击一个路口\n2. 使用此工具来控制红绿灯 TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool 路边停车限制 @@ -232,25 +232,25 @@ Scan_for_known_incompatible_mods_on_startup 启动时检测是否存在已知不 Ignore_disabled_mods 忽略未启用MOD Traffic_Manager_detected_incompatible_mods 检测到的不兼容MOD Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 -Unsubscribe Unsubscribe -Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu -Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool -Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool -Keybind_use_lane_connections_tool Use 'Lane Connections' Tool -Keybind_use_priority_signs_tool Use 'Priority Signs' Tool -Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool -Keybind_use_speed_limits_tool Use 'Speed Limits' Tool -Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane -Keybind_lane_connector_delete Clear lane connections -Keybinds Keybinds -Keybind_Exit_subtool Exit tool and close TM:PE Menu -Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. -Keybind_category_Global Global keys -Keybind_category_LaneConnector Lane Connector Tool keys -Display_speed_limits_mph Display speed limits as MPH instead of km/h -Miles_per_hour Miles per Hour -Kilometers_per_hour Kilometers per Hour -Road_signs_theme_mph Theme for MPH road signs -theme_Square_US US signs -theme_Round_UK British signs -theme_Round_German German signs +Unsubscribe 取消订阅 +Keybind_toggle_TMPE_main_menu TM:PE主菜单 +Keybind_toggle_traffic_lights_tool 「红绿灯增删」工具 +Keybind_use_lane_arrow_tool 「变更车道方向」工具 +Keybind_use_lane_connections_tool 「车道连接」工具 +Keybind_use_priority_signs_tool 「优先通行权设置」工具 +Keybind_use_junction_restrictions_tool 「路口管制」工具 +Keybind_use_speed_limits_tool 「道路限速设置」工具 +Keybind_lane_connector_stay_in_lane 车道连接:stay in lane +Keybind_lane_connector_delete 清除车道连接设置 +Keybinds 热键 +Keybind_Exit_subtool 关闭工具与TM:PE主菜单 +Keybind_conflict 所选快捷键有冲突,请重新选择。 +Keybind_category_Global 全局热键 +Keybind_category_LaneConnector 车道连接工具热键 +Display_speed_limits_mph 速度以MPH而不是km/h显示 +Miles_per_hour 英里/小时 +Kilometers_per_hour 千米/小时 +Road_signs_theme_mph MPH路标风格 +theme_Square_US 美国风格 +theme_Round_UK 英国风格 +theme_Round_German 德国风格 From 816d83bf93ca20f7ec7cc1d10c3dc6b01976c60c Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Sat, 6 Jul 2019 06:33:35 +0100 Subject: [PATCH 134/142] Refactor changelog - Add missing early versions of mod (Traffic Manager, Traffic Manager Plus) - Ensure all items are correctly & consistently prefixed - Order each changelog by prefix importance (end-user perspective) - Remove `@` in front of names (most are not GitHub users) - Add in C:SL game updates stating stuff relevant to TM:PE - ...and ensure each subsequent version of TM:PE is marked compatible - Fix load of typos, ensure high degree of consistency - Consistency will allow easier styling if we put it on website (future task) --- CHANGELOG.md | 2327 +++++++++++++++++++++++++++++++------------------- 1 file changed, 1432 insertions(+), 895 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68017887d..05b5d4e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ # Cities: Skylines - Traffic Manager: *President Edition* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) # Changelog -### [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), 02/07/2019 + +> **Legend:** +> +> * **C:SL** = Cities: Skylines game updates +> * **TM:PE** = Traffic Manager: President Edition +> * **TMPlus** = Traffic Manager Plus +> * **TM** = Traffic Manager + +### TM:PE [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), 02/07/2019 + - Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) - Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) - Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) @@ -9,7 +18,7 @@ - Added: Keybinds tab in mod options - choose your own shortcuts! (thanks kvakvs) (#382) - Added: Show keyboard shortcuts in button tooltips where applicable (thanks kvakvs) (#382) - Added: Basic support of offline mode for users playing on EA's Origin service (#333, #400) -- Improved: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211, #400) +- Improved:: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211, #400) - Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid) (#362) - Fixed: Mail trucks ignoring lane arrows (thanks Subaru & eudyptula for feedback) (#307, #338) - Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) @@ -39,897 +48,1425 @@ - Meta: Updated install guide to include section for EA Origin users (#333) - Meta: Enable latest C# `LangVersion` in all projects (#398) -### 10.20, 21/05/2019 -- Updated for game version 1.12.0-f5 -- Updated Korean translation (thanks Twotoolus-FLY-LShst) (#294) -- Updated French translation (thanks PierreTSE) (#311) - -### 10.19, 20/04/2019 -- Bugfix: Mod options overlapping issue (#250, #266). -- Added: Japanese language (thanks mashitaro) (#258). -- Update: Chinese language (thanks Emphasia) (#285, #286). -- Update: "Vanilla Trees Remover" as incompatible mod (it breaks mod options screen) (#271, #290). -- Update: Moved "Delete" step button on timed traffic lights (#283, #285). -- Update: Mod incompatibility checker can now be disabled, or skip disabled mods (#264, #284, #286). - -### 10.18, 29/03/2019 -- Bugfix: Parking AI: Cars do not spawn at outside connections (#245) -- Bugfix: Trams perform turns on red (#248) -- Update: Service Radius Adjuster mod by Egi removed from incompatible mods list (#255) - -### 10.17, 23/03/2019 -- Introduced new versioning scheme (10.17 instead of 1.10.17) -- Synchronized code and version with stable version -- Updated russian translation (thanks to @vitalii201 for translating) (#207) -- Updated list of incompatible mods (#115) -- Removed stable version from list of incompatible mods (#168) -- Turn-on-red can now be toggled for unpreferred turns between one-ways -- Improved train behavior at shunts: Trains now prefer to stay on their track (#230) -- Fixed and optimized lane selection for u-turns and at dead ends (#101) -- Parking AI: Improved public transport (PT) usage patterns, mixed car/PT paths are now possible (#218) -- Bugfix: Parking AI: Tourist cars despawn because they assume they are at an outside connection (#218) -- Bugfix: Parking AI: Return path calculation did not accept beautification segments (#218) -- Bugfix: Parking AI: Cars/Citizens waiting for a path might jump around (#218) -- Bugfix: Vanilla lane randomization does not work as intended at highway transitions (#112) -- Bugfix: Vehicles change lanes at tollbooths (#225) -- Bugfix: Path-finding: Array index is out of range due to a race condition (#221) -- Bugfix: Citizen not found errors when using walking tours (#219) -- Bugfix: Timed light indicator only visible when any timed light node is selected (#222) - -### 1.10.16, 24/02/2019 -- Gameplay: Fixed problem with vehicle despawn after road upgrade/remove (thanks @pcfantasy for implementation suggestion)(#86, #101) -- Gameplay: Fixed problem with vehicles unable to choose lane when u-turn at dead-end (thanks @pcfantasy for implementation and @aubergine10 for neccesary tests)(#101) -- Gameplay: Fixed problem when user couldn't change state of 'Turn on Red' while enabled_by_default option not selected (thanks @Sp3ctre18 for bug confirmation) (#102) -- Gameplay: Added missing logic for noise density calculations (thanks to @pcfantasy for fix) (#66) -- UI: New icons for empty and remove_priority_sign settings (thanks @aubergine10 for those icons) (#75, #77) -- Other: Greatly improved incompatible mod scanner, added dialog to list and unsubscribe incompatible mods (#91) -- Other: Changed mod name in Content Manager to __TM:PE__ -- Other: Discord server was set up by @FireController1847 - link in mod description -- Other: Fixed 'silent error' inside log related with "Esc key handler" (#92) -- Contribution: Added project building instructions and PR review - -### 1.10.15, 10/02/2019 -- Enhancement: Now you can use Escape key to close Traffic Manager without returning to Pause Menu (thanks to @aubergine10 for suggestion) (#16) -- Gameplay: Updated pathfinding with missing vanilla logic -- Gameplay: Tweaked values in CargoTruckAI path finding (thanks to @pcfantasy for improvement suggestion) -- Gameplay: Tweaked speed multiplier of reckless drivers to get more realistic speed range (thanks to @aubergine10 for suggestion) (#23) -- UI: New icons for cargo and passenger train restriction (thanks to @aubergine10) (#17) -- Translations: Simplified Chinese translation updated (thanks to @Emphasia for translating) -- Other: Added notification if user is still subscribed to old original TM:PE -- [Experimental feature] Turn on red (thanks to @FireController1847 for implementation and to @pcfantasy for source code base) - -### 1.10.14, 27/01/2019 -- Bugfix: Added missing Car AI type (postVanAI) - now post vans and post trucks are assigned to service vehicles group -- Bugfix: Vehicles doesn't stop when driving through toll booth - fixes toll booth income too -- Bugfix: Cargo Airport doesn't work (Cargo planes not spawning and not arriving) -- Updated Polish translation -- Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating) -- Fixed Mod Options layout (text label overlaps slider control if too wide) - -### 1.10.13, 31/10/2018 -- Bugfix: Tollbooth fix - -### 1.10.12, 08/12/2018 -- Added the option to allow/disallow vehicles to enter a blocked junction at transition and pedestrian crossing nodes (#195) -- Updated Russian translation (thanks to vitalii2011 for translating) -- Bent nodes do not allow for u-turns by default (#170) -- Bugfix: Emergency vehicles pass closed barriers at level crossings -- Bugfix: Bus lines render u-turn where they should not (#207) -- Bugfix: Parking AI: Cims leaving the city despawn their car at public transport stations (#214) -- Bugfix: Crossing restrictions do not work at intersection between road and highway (#212) - -### 1.10.11, 21/07/2018 -- U-turn lane connections are represented by appropriate lane arrow (#201) -- Bugfix: Heavy vehicles are unable to u-turn at dead ends (#194) -- Bugfix: Routing & Priority rules do not work properly for acute (< 30°)/obtuse(> 150°) segment angles (#199) -- Bugfix: Buses do not prefer lanes with correct lane arrow (#206) -- Bugfix: Race condition in path-finding might cause paths to be assigned to wrong vehicle/citizen (#205) -- Bugfix: Vehicles are unable to perform u-turns when setting off on multi-lane roads (#197) - -### 1.10.10, 14/07/2018 -- Parking AI: Improved park & ride behavior -- Parking AI: Walking paths from parking position to destination building take public transportation into account -- Bugfix: Parking AI causes unnecessary path-findings (#183, thanks to Sipke82 for reporting) -- Bugfix: Prohibiting cims from crossing the road also affect paths where crossing is unnecessary (#168, thanks to aubergine10 for reporting) - -### 1.10.9, 13/07/2018 -- Updated for game version 1.10.1-f3 -- Re-implemented path-finding algorithm -- Updated French translation (thanks to mjm92150 for translating!) - -### 1.10.8, 01/07/2018 -- Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating) -- Updated Polish translation (thanks to @Krzychu1245 for translating) -- Added button to remove parked vehicles (in options dialog, see maintenance tab) -- Parking AI: Removed check for distance between parked vehicle and target building -- Bugfix: Parking AI: Cims spawn pocket cars when they originate from an outside connection -- Bugfix: Incorrect speed limits returned for pedestrian lanes -- Bugfix: Routing is not updated while the game is paused (thanks to @Oh My Lawwwd! for reporting) -- Bugfix: Vanilla traffic lights are ignored when either the priority signs or timed traffic light features are disabled (thanks to @aubergine10 for reporting) -- Bugfix: Park maintenance vehicles are not recognized as service vehicles -- Bugfix: Cars leaving city state "thinking of a good parking spot" (thanks to @aubergine10 for reporting) - -### 1.10.7, 28/05/2018 -- Bugfix: U-turn routing is inconsistent on transport lines vs. bus paths (#137, thanks to @Zorgoth for reporting this issue) -- Bugfix: Junction restrictions for pedestrian crossings are sometimes not preserved (#142, thanks to Anrew and @wizardrazer for reporting this issue) +### C:SL 1.12.1-f2, 04/06/2019 + +- Fixed: Numerous bugs in Campus DLC + +### TM:PE 10.20, 21/05/2019 + +- Updated: Compatible with C:SL 1.12.0-f5 +- Updated: Korean translation (thanks Twotoolus-FLY-LShst) (#294) +- Updated: French translation (thanks PierreTSE) (#311) + +### C:SL 1.12.0-f5 (Campus), 21/05/2019 + +- Added: University areas & lots of buildings +- Added: Choose bus to use on bus lines + +### TM:PE 10.19, 20/04/2019 + +- Fixed: Mod options overlapping issue (#250, #266). +- Updated: Moved "Delete" step button on timed traffic lights (#283, #285). +- Updated: "Vanilla Trees Remover" as incompatible mod (it breaks mod options screen) (#271, #290). +- Updated: Mod incompatibility checker can now be disabled, or skip disabled mods (#264, #284, #286). +- Updated: Chinese language (thanks Emphasia) (#285, #286). +- Updated: Japanese language (thanks mashitaro) (#258). + +### TM:PE 10.18, 29/03/2019 + +- Fixed: Parking AI: Cars do not spawn at outside connections (#245) +- Fixed: Trams perform turns on red (#248) +- Updated: Service Radius Adjuster mod by Egi removed from incompatible mods list (#255) + +### TM:PE 10.17, 23/03/2019 + +- Added: Turn-on-red can now be toggled for unpreferred turns between one-ways +- Improved: Train behavior at shunts: Trains now prefer to stay on their track (#230) +- Improved: Parking AI - Improved public transport (PT) usage patterns, mixed car/PT paths are now possible (#218) +- Fixed: Lane selection for u-turns and at dead ends (also optimised) (#101) +- Fixed: Parking AI - Tourist cars despawn because they assume they are at an outside connection (#218) +- Fixed: Parking AI - Return path calculation did not accept beautification segments (#218) +- Fixed: Parking AI - Cars/Citizens waiting for a path might jump around (#218) +- Fixed: Vanilla lane randomization does not work as intended at highway transitions (#112) +- Fixed: Vehicles change lanes at tollbooths (#225) +- Fixed: Path-finding: Array index is out of range due to a race condition (#221) +- Fixed: Citizen not found errors when using walking tours (#219) +- Fixed: Timed light indicator only visible when any timed light node is selected (#222) +- Updated: Compatible with C:SL 1.11.1-f4 +- Updated: Synchronized code and version with stable version +- Updated: List of incompatible mods (#115) +- Updated: Russian translation (thanks to vitalii201 for translating) (#207) +- Removed: Stable version from list of incompatible mods (#168) +- Meta: Introduced new versioning scheme (10.17 instead of 1.10.17) + +### C:SL 1.11.1-f4, 27/02/2019 + +- Fixed: Remove duplicate map + +### TM:PE 1.10.16, 24/02/2019 + +- Improved: New icons for empty and remove_priority_sign settings (thanks aubergine10 for those icons) (#75, #77) +- Fixed: Problem with vehicle despawn after road upgrade/remove (thanks pcfantasy for implementation suggestion)(#86, #101) +- Fixed: problem with vehicles unable to choose lane when u-turn at dead-end (thanks pcfantasy for implementation and aubergine10 for neccesary tests)(#101) +- Fixed: problem when user couldn't change state of 'Turn on Red' while enabled_by_default option not selected (thanks Sp3ctre18 for bug confirmation) (#102) +- Fixed: Fixed 'silent error' inside log related with "Esc key handler" (#92) +- Updated: Greatly improved incompatible mod scanner, added dialog to list and unsubscribe incompatible mods (#91) +- Updated: Changed mod name in Content Manager to __TM:PE__ +- Updated: Added missing logic for noise density calculations (thanks to pcfantasy for fix) (#66) +- Meta: Discord server was set up by FireController1847 - link in mod description +- Meta: Added project building instructions and PR review + +### TM:PE 1.10.15, 10/02/2019 + +- Added: (Experimental) Turn on red (thanks to FireController1847 for implementation and to pcfantasy for source code base) +- Added: Notification if user is still subscribed to old original TM:PE +- Improved: Use Escape key to close Traffic Manager without returning to Pause Menu (thanks to aubergine10 for suggestion) (#16) +- Improved: New icons for cargo and passenger train restriction (thanks to aubergine10) (#17) +- Updated: Updated pathfinding with missing vanilla logic +- Updated: Tweaked values in CargoTruckAI path finding (thanks to pcfantasy for improvement suggestion) +- Updated: Tweaked speed multiplier of reckless drivers to get more realistic speed range (thanks to aubergine10 for suggestion) (#23) +- Updated: Simplified Chinese translation updated (thanks to Emphasia for translating) + +### TM:PE 1.10.14, 27/01/2019 + +- Fixed: Added missing Car AI type (postVanAI) - now post vans and post trucks are assigned to service vehicles group +- Fixed: Vehicles doesn't stop when driving through toll booth - fixes toll booth income too +- Fixed: Cargo Airport doesn't work (Cargo planes not spawning and not arriving) +- Fixed: Mod Options layout (text label overlaps slider control if too wide) +- Updated: Compatible with C:SL 1.11.1-f2 +- Updated: Polish translation +- Updated: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) + +### C:SL 1.11.1-f2 (Holiday Surprise Patch), 13/12/2018 + +- Fixed: Cargo planes circle and use buildings that do not otherwise have any plane traffic +- Fixed: Vehicles can't cross the center line of Small Industry roads +- Fixed: Various bugs in game and DLCs + +### TM:PE 1.10.13, 31/10/2018 + +- Fixed: Toll booth not working +- Meta: Roads United Core also breaks toll booths + +### TM:PE 1.10.12, 08/12/2018 + +- Added: Allow/disallow vehicles to enter a blocked junction at transition and pedestrian crossing nodes (#195) +- Fixed: Emergency vehicles pass closed barriers at level crossings +- Fixed: Bus lines render u-turn where they should not (#207) +- Fixed: Parking AI - Cims leaving the city despawn their car at public transport stations (#214) +- Fixed: Crossing restrictions do not work at intersection between road and highway (#212) +- Updated: Compatible with C:SL 1.11.0-f3 +- Updated: Bent nodes do not allow for u-turns by default (#170) +- Updated: Russian translation (thanks to vitalii2011 for translating) + +### C:SL 1.11.0-f3 (Industries), 23/10/2018 + +- Added: Toll booths +- Added: Postal service, vans and trucks +- Added: Additional industry vehicles +- Added: Cargo airport and planes +- Added: Warehouses and storage buildings +- Fixed: Bugs in various DLCs + +### TM:PE 1.10.11, 21/07/2018 + +- Updated: U-turn lane connections are represented by appropriate lane arrow (#201) +- Fixed: Heavy vehicles are unable to u-turn at dead ends (#194) +- Fixed: Routing & Priority rules do not work properly for acute (< 30°)/obtuse(> 150°) segment angles (#199) +- Fixed: Buses do not prefer lanes with correct lane arrow (#206) +- Fixed: Race condition in path-finding might cause paths to be assigned to wrong vehicle/citizen (#205) +- Fixed: Vehicles are unable to perform u-turns when setting off on multi-lane roads (#197) + +### TM:PE 1.10.10, 14/07/2018 + +- Improved: Parking AI - Improved park & ride behaviour +- Fixed: Parking AI causes unnecessary path-findings (#183, thanks to Sipke82 for reporting) +- Fixed: Prohibiting cims from crossing the road also affect paths where crossing is unnecessary (#168, thanks to aubergine10 for reporting) +- Updated: Parking AI - Walking paths from parking position to destination building take public transportation into account + +### TM:PE 1.10.9, 13/07/2018 + +- Updated: Compatible with C:SL 1.10.1-f3 +- Updated: Re-implemented path-finding algorithm +- Updated: French translation (thanks to mjm92150 for translating!) + +### C:SL 1.10.1-f3, 04/07/2018 + +- Fixed: Various bugs in game and DLCs + +### TM:PE 1.10.8, 01/07/2018 + +- Added: Button to remove parked vehicles (in options dialog, see maintenance tab) +- Fixed: Parking AI - Cims spawn pocket cars when they originate from an outside connection +- Fixed: Incorrect speed limits returned for pedestrian lanes +- Fixed: Routing is not updated while the game is paused (thanks to Oh My Lawwwd! for reporting) +- Fixed: Vanilla traffic lights are ignored when either the priority signs or timed traffic light features are disabled (thanks to aubergine10 for reporting) +- Fixed: Park maintenance vehicles are not recognized as service vehicles +- Fixed: Cars leaving city state "thinking of a good parking spot" (thanks to aubergine10 for reporting) +- Updated: Parking AI - Removed check for distance between parked vehicle and target building +- Updated: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) +- Updated: Polish translation (thanks to Krzychu1245 for translating) + +### TM:PE 1.10.7, 28/05/2018 + +- Fixed: U-turn routing is inconsistent on transport lines vs. bus paths (#137, thanks to Zorgoth for reporting this issue) +- Fixed: Junction restrictions for pedestrian crossings are sometimes not preserved (#142, thanks to Anrew and wizardrazer for reporting this issue) - Fixed: Geometry subscription feature may cause performance issues (#145) -- Fixed: Parking AI: Transport mode storage causes performance issues during loading (#147, thanks to @hannebambel002 and @oneeyets for reporting and further for providing logs and savegames) - -### 1.10.6, 24/05/2018 -- Updated for game version 1.10.0-f3 -- Accessibility: New option: Main menu size can be controlled -- Accessibility: New option: GUI and overlay transparency can be controlled -- New option: Penalties for switching between different public transport lines can be toggled -- Cims can now be removed from the game -- Improved window design -- Path-finding: Service vehicles are now allowed to ignore lane arrows right after leaving their source building, thus service buildings should now work properly at dead-end roads with median -- Lane connector can be used on monorail tracks -- Advanced Vehicle AI: Tuned parameters -- Dynamic Lane Selection: Absolute speed measurements are used instead of relative measurements -- Improved randomization for realistic speeds such that vehicles may change their target velocity over time -- Improved vehicle position tracking -- Improved mod compatibility checks -- Parking AI: Improved behavior in situations where vehicles are parked near public transport hubs and road connections are partially unavailable -- Bugfix: Parking AI: Not all possible paths are regarded during path-finding -- Bugfix: Parking AI: Cims become confused when trying to return their abandoned car back home (special thanks to Wildcard-25 for reporting and solving this issue) -- Bugfix: Parking AI: Cims do not search for parking building when road-side parking spaces are found -- Bugfix: Parking AI: Parked vehicles are spawned near the source building even when cims are already en route -- Bugfix: Parking AI: Cims sometimes get stuck in an infinite loop while trying to enter their parked car -- Bugfix: Lane connector does not work for roads with more than ten lanes -- Bugfix: Allowing/Disallowing vehicles to enter a blocked junction does not work for certain junctions -- Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating) - -### 1.10.5, 06/01/2018 -- UI scaling removed -- Simplified Chinese translation updated (thanks to Emphasia for translating) -- Polish translation updated (thanks to @Krzychu1245 for translating) -- Introduced randomization for lane changing costs -- Introduced randomization for "trucks prefer innermost lanes on highways" costs -- Removed unnecessary calculations in path-finding -- Added path-finding costs for public transport transitions -- Pedestrian traffic lights do not show up if crossing the street is prohibited -- Busses are allowed to switch multiple lanes after leaving a bus stop -- Bugfix: Main menu button might be out of view -- Bugfix: Division by zero occurs for low speed roads -- Bugfix: Automatic pedestrian lights at railroad do not work as expected -- Bugfix: Timed traffic lights show up for bicycles (they should not) -- Bugfix: Due to a multi-threading issue junction restrictions may cause the game state to become inconsistent -- Bugfix: Routing rules prevents vehicles from spawning when starting building lies too close to an intersection/road end -- Bugfix: Disabling tutorial message has no effect -- Bugfix: "Stay on lane" feature does not work as intended for certain nodes - -### 1.10.4, 19/10/2017 -- Updated for game version 1.9.0-f5 -- Added possibility to add priority signs at multiple junctions at once (press Shift) -- Added tutorials (can be disabled in the options window globally) - -### 1.10.3, 18/08/2017 -- Bugfix: Setting unlimited speed limit causes vehicles to crawl at low speed (thanks to @sethisuwan for reporting this issue) -- Bugfix: Vehicle-separated traffic lights do not show up for trams & monorails (thanks to @thecitiesdork for reporting this issue) - -### 1.10.2, 17/08/2017 -- Updated for game version 1.8.0-f3 -- Improved performance -- Bugfix: Pedestrians sometimes ignore red traffic light signals (thanks to @(c)RIKUPI™ for reporting this issue) -- Bugfix: Timed traffic lights do not correctly recognize set vehicle restrictions (thanks to @alborzka for reporting this issue) - -### 1.10.1, 05/08/2017 -- Updated Polish, Korean, and Simplified Chinese translations -- Bugfix: Default routing is disabled if the lane connector is used on a subset of all available lanes only -- Bugfix: Parking AI cannot be enabled/disabled -- Bugfix: Lane connection points can connected to themselves - -### 1.10.0, 30/07/2017 -- New feature: Dynamic Lane Selection -- New feature: Adaptive step switching -- New feature: Individual vehicles may be removed from the game -- New option: Vehicle restrictions aggression -- New option: Vehicles follow priority rules at junctions with timed traffic lights -- Improved path-finding performance -- Improved traffic measurement engine performance -- Reorganized global configuration file (sorry, your main menu and main button positions are reset) -- The option "Road condition has a bigger impact on vehicle speed" is only shown if the Snowfall DLC is owned -- The flow/wait calculation mode to be used is now configurable via the global configuration file -- Added path-find statistics label -- Added confirmation dialog for "Clear Traffic" button -- Currently active timed traffic light step is remembered -- Trains do not wait for each other anymore near timed traffic lights -- It is now possible to connect train station tracks and outside connections with the lane connector -- Disabling the Parking AI triggers graceful clean up procedure -- Relocated some options -- Improved vehicle state tracking -- Workaround for a base game issue that causes trams to get stuck -- Trains do not longer stop in front of green timed traffic lights -- Vehicles use queue skipping to prioritize path-finding runs that are caused by road modifications -- Adding a vehicle separate light to a timed traffic lights applies the main light configuration -- Parking AI: Vehicles can now find parking spaces at the opposite road side -- Parking AI: Included an improved fallback logic for some edge cases -- Parking AI: Citizens should now be more successful in returning their cars back home -- Parking AI: Tuned parking radius parameters -- Parking AI: If the limit for parked vehicles is reached and parking fails due to it, no alternative parking space is queried -- Vehicle AI: Busses prefer lanes with correct lane arrow over incorrect ones -- Bugfix: Using the bulldozer tool might lead to inconsistent road geometry information -- Bugfix: Citizens that fail to approach their parked car fly towards their target building -- Bugfix: Parking AI: Path-finding fails if cars are parked too far away from a road -- Bugfix: Parking AI: Citizens approaching a car start to float away -- Bugfix: "Heavy vehicles prefer outer lanes on highways" does not work -- Bugfix: The lane connector does not allow connecting all available lane end points at train stations and on bidirectional one-lane train tracks -- Bugfix: Vehicles may get stuck in several situations -- Upgrading to a road with bus lanes now copies an already existing traffic light state to the new traffic light - -### 1.9.6, 28/05/2017 -- Updated Simplified Chinese translation -- Bugfix: Vehicles cannot perform u-turns at junctions with only one outgoing segment (thanks to @Sunbird for reporting this issue) -- Bugfix: Path-finding costs for large distances exceed the maximum allowed value (thanks to @Huitsi for reporting this issue) -- Bugfix: Under certain circumstances path-finding at railroad crossings allow switching from road to rail tracks. - -### 1.9.5, 24/05/2017 -- Updated for game version 1.7.1-f1 -- Updated Polish, Korean and Italian translation -- Language can now be switched without requiring a game restart -- Bugfix: Routing calculation does not work as expected for one-way roads with tram tracks (thanks to @bigblade66, @Battelman2 and @AS_ for reporting and providing extensive information) -- Bugfix: Copying timed traffic lights lead to inconsistent internal states which causes timed traffic lights to be omitted during the save process (thanks to @jakeroot and @t1a2l for reporting this issue) -- Bugfix: In certain situations unnecessary vehicle-seperate traffic lights are being created -- Bugfix: Upgrading a train track segment next to a timed traffic light causes trains to ignore the traffic light -- Hotfix: Cable cars despawn at end-of-line stations - -### 1.9.4, 23/05/2017 -- New option: Ban private cars and trucks on bus lanes -- Updated Spanish and French translation -- Optimized path-finding -- Increased path-finding cost for private cars driving on bus lanes -- Increased path-finding cost for disregarding vehicle restrictions -- Bugfix: Path-finding is unable to calculate certain paths after modifying the road network - -### 1.9.3, 22/05/2017 -- Disabled notification of route recalculating because some players report crashes -- Removed default vehicle restrictions from bus lanes -- Modified junction restrictions come into effect instantaneously -- UI: Saving a timed step does not reset the timed traffic light to the first state -- Bugfix: AI: Segment traffic data is not taken into account -- Bugfix: Priority rules are not properly obeyed -- Bugfix: Under certain circumstances priority signs cannot be removed -- Bugfix: Path-finding is unable to calculate certain paths - -### 1.9.2, 20/05/2017 -- UI: Main menu & UI tools performance improved -- Bugfix: Traffic lights can be removed from junctions that are controlled by a timed traffic light program - -### 1.9.1, 19/05/2017 -- Updated French, Dutch and Korean translation -- Bugfix: Using the vanilla traffic light toggling feature crashes the game if TMPE's main menu has not been opened at least once -- Bugfix: AI: More car traffic and less public transportation present than in vanilla - -### 1.9.0, 18/05/2017 -- Updated for game version 1.7.0-f5 -- New feature: Parking restrictions -- New feature: Speed limits can be set up for individual lanes with the Control key -- New feature: Added timed traffic light and speed limit support for monorails -- New feature: Copy & paste for individual timed traffic lights -- New feature: Rotate individual timed traffic lights -- New feature: Lane customizations may come into effect instantaneously -- Unified traffic light toggling feature with game code -- Performance improvements -- Reworked the way that traffic measurements are performed -- Advanced Vehicle AI: Algorithm updated, performance improved - Possible routing decisions are now being precalculated -- Path-finding cost multiplicator for vehicle restrictions is now configurable in TMPE_GlobalConfig.xml -- UI: More compact, movable main menu UI -- Added support for custom languages -- Added Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating) -- Updated translations: German, Polish, Russian, Portuguese, Traditional Chinese -- Major code refactorings -- AI: Tuned path-finding parameters -- New option: Main button position can be locked -- New option: Main menu position can be locked -- New option: Added language selection in options dialog -- New option: Customization of lane arrows, lane connections and vehicle restrictions can now come into effect instantaneously -- Bugfix: Cars sometimes get stuck forever when the Advanced Parking AI is activated (thanks to @cmfcmf for reporting this issue) -- Bugfix: Busses do not perform u-turns even if the transport line show u-turns (thanks to @dymanoid for reporting this issue) -- Bugfix: Timed traffic lights do not work as expected on single-direction train tracks (thanks to @DaEgi01 for reporting this issue) -- Bugfix: Vehicle restriction and speed limit signs overlay is displayed on the wrong side of inverted road segments -- Bugfix: Influx statistics value is zero (thanks to @hjo for reporting this issue) - -### 1.8.16, 20/03/2017 -- Lane connections can now also be removed by pressing the backspace key -- Improved lane selection for busses if the option "Busses may ignore lane arrows" is activated -- Bugfix: The game sometimes freezes when using the timed traffic light tool -- Bugfix: Lane connections are not correctly removed after modifying/removing a junction -- Bugfix: Selecting a junction for setting up junction restrictions toggles the currently hovered junction restriction icon - -### 1.8.15, 27/01/2017 -- Updated for game version 1.6.3-f1 - -### 1.8.14, 07/01/2017 -- Bugfix: Wait/flow ratio at timed traffic lights is sometimes not correctly calculated -- Bugfix: A deadlock situation can arise at junctions with priority signs such that no vehicle enters the junction -- Bugfix: When adding a junction to a timed traffic light, sometimes light states given by user input are not correctly stored -- Bugfix: Joining two timed traffic lights sets the minimum time to "1" for steps with zero minimum time assigned -- Bugfix: Modifications of timed traffic light states are sometimes not visible while editting the light (but they are applied nonetheless) -- Bugfix: Button background is not always correctly changed after clicking on a button within the main menu -- Tram lanes can now be customized by using the lane connector tool -- Minor performance optimizations for priority sign simulation - -### 1.8.13, 05/01/2017 -- Bugfix: Timed traffic ligt data can become corrupt when upgrading a road segment next to a traffic light, leading to faulty UI behavior (thanks to @Brain for reporting this issue) -- Bugfix: The position of the main menu button resets after switching to the free camera mode (thanks to @Impact and @gravage for reporting this issue) -- Bugfix: A division by zero exception can occur when calculating the average number of waiting/floating vehicles -- Improved selection of overlay markers on underground roads (thanks to @Padi for reminding me of that issue) -- Minor performance improvements - -### 1.8.12, 02/01/2017 -- Updated for game version 1.6.2-f1 -- Bugfix: After leaving the "Manual traffic lights" mode the traffic light simulation is not cleaned up correctly (thanks to @diezelunderwood for reporting this issue) -- Bugfix: Insufficient access rights to log file causes the mod to crash - -### 1.8.11, 02/01/2017 -- Bugfix: Speed limits for elevated/underground road segments are sometimes not correctly loaded (thanks to @Pirazel and @[P.A.N] Uf0 for reporting this issue) - -### 1.8.10, 31/12/2016 -- Improved path-finding performance (a bit) -- Added a check for invalid road thumbnails in the "custom default speed limits" dialog - -### 1.8.9, 29/12/2016 -- It is now possible to set speed limits for metro tracks -- Custom default speed limits may now be defined for train and metro tracks -- Junction restrictions may now be controlled at bend road segments -- Customizable junctions are now highlighted by the lane connector tool -- Improved UI behavior -- Performance improvements -- Bugfix: Selecting a junction to set up priority signs sometimes does not work (thanks to @Artemis *Seven* for reporting this issue) -- Bugfix: Automatic pedestrian lights do not work as expected at junctions with incoming one-ways and on left-hand traffic maps - -### 1.8.8, 25/12/2016 -- Bugfix: Taxis are not being used -- Bugfix: Prohibiting u-turns with the junction restriction tool does not work (thanks to @Kisoe for reporting this issue) -- Bugfix: Cars are sometimes floating across the map while trying to park (thanks to @[Delta ²k5] for reporting this issue) - -### 1.8.7, 24/12/2016 -- Bugfix: Parking AI: Cims that try to reach their parked car are sometimes teleported to another location where they start to fly through the map in order to reach their car -- Bugfix: Parking AI: Cims owning a parked car do not consider using other means of transportation -- Bugfix: Parking AI: Residents are unable to leave the city through a highway outside connection -- Bugfix: Trains/Trams are sometimes not detected at timed traffic lights -- Advanced AI: Improved lane selection -- The position of the main menu button is now forced inside screen bounds on startup -- Improved overall user interface performance -- Improved overlay behavior -- Improved traffic measurement -- Auto pedestrian lights at timed traffic lights behave more intelligently now -- A timed traffic light step with zero minimum time assigned can now be skipped automatically -- Using the lane connector to create a u-turn now automatically enables the "u-turn allowed" junction restriction -- Updated French translation (thanks to @simon.royer007 for translating) -- Added Italian translation (thanks to @Admix for translating) - -### 1.8.6, 12/12/2016 -- Added Korean language (thanks to @Toothless FLY [ROK]LSh.st for translating) -- Updated Chinese language code (zh-cn -> zh) in order to make it compatible with the game (thanks to @Lost丶青柠 for reporting this issue) - -### 1.8.5, 11/12/2016 -- Updated to game version 1.6.1-f2 -- Removed option "Evacuation busses may only be used to reach a shelter" (CO fixed this issue) -- Bugfix: Average speed limits are not correctly calculated for road segments with bicycle lanes (thanks to @Toothless FLY [ROK]LSh.st for reporting this issue) - -### 1.8.4, 11/12/2016 -- New feature: "Stay on lane": By pressing Shift + S in the Lane Connector tool you can now link connected lanes such that vehicles are not allowed to change lanes at this point. Press Shift + S again to restrict "stay on lane" to either road direction. -- U-turns are now only allowed to be performed from the innermost lane -- TMPE now detects if the number of spawned vehicles is reaching its limit (16384). If so, spawning of service/emergency vehicles is prioritized over spawning other vehicles. -- Bugfix: Bicycles cannot change from bicycle lanes to pedestrian lanes -- Bugfix: Travel probabilities set in the "Citizen Lifecycle Rebalance v2.1" mod are not obeyed (thanks to @informmanuel, @shaundoddmusic for reporting this issue) -- Bugfix: Number of tourists seems to drop when activating the mod (statistics were not updated, thanks to @hpp7117, @wjrohn for reporting this issue) -- Bugfix: When loading a second savegame a second main menu button is displayed (thanks to @Cpt. Whitepaw for reporting this issue) -- Bugfix: While path-finding is in progress vehicles do "bungee-jumping" on the current segment (thanks to @mxolsenx, @Howzitworld for reporting this issue) -- Bugfix: Cims leaving the city search for parking spaces near the outside connection which is obviously not required - -### 1.8.3, 4/12/2016 -- Bugfix: Despite having the Parking AI activated, cims sometimes still spawn pocket cars. -- Bugfix: When the Parking AI is active, bicycle lanes are not used (thanks to @informmanuel for reporting this issue) -- Tweaked u-turn behavior -- Improved info views - -### 1.8.2, 3/12/2016 -- Bugfix: Taxis were not used (thanks to @[Delta ²k5] for reporting) -- Bugfix: Minor UI fix in Default speed limits dialog - -### 1.8.1, 1/12/2016 -- Updated translations: Polish, Chinese (simplified) -- Bugfix: Mod crashed when loading a second savegame - -### 1.8.0, 29/11/2016 -- Updated to game version 1.6.0-f4 -- New feature: Default speed limits -- New feature: Parking AI (replaces "Prohibit cims from spawning pocket cars") -- New option: Heavy vehicles prefer outer lanes on highways -- New option: Realistic speeds -- New option: Evacuation busses may ignore traffic rules (Natural Disasters DLC required) -- New option: Evacuation busses may only be used to reach a shelter (Natural Disasters DLC required) -- AI: Improved lane selection, especially on busy roads -- AI: Improved mean lane speed measurement -- Traffic info view shows parking space demand if Parking AI is activated -- Public transport info view shows transport demand if Parking AI is activated -- Added info texts for citizen and vehicle tool tips if Parking AI is activated -- Extracted internal configuration to XML configuration file -- Changed main menu button due to changes in the game's user interface -- Main menu button is now moveable -- Removed compatibility check for Traffic++ V2 (Traffic++ V2 is no longer compatible with TMPE because maintaining compatibility is no longer feasible due to the high effort) -- Updated translations: German, Portuguese, Russian, Dutch, Chinese (traditional) - -### 1.7.15, 26/10/2016 -- Bugfix: Timed traffic lights window disappears when clicking on it with the middle mouse button (thanks to @Nexus and @Mariobro14 for helping me identifying the cause of this bug) - -### 1.7.14, 18/10/2016 -- Updated for game version 1.5.2-f3 - -### 1.7.13, 15/09/2016 -- Implemented a permanent fix to solve problems with stuck vehicles/cims caused by third party mods -- Added a button to reset stuck vehicles/cims (see mod settings menu) -- AI: Improved lane selection algorithm -- Bugfix: AI: Lane merging was not working as expected -- Bugfix: Pedestrian light states were sometimes not being stored correctly (thanks to Filip for pointing out this problem) - -### 1.7.12, 09/09/2016 -- AI: Lane changes are reduced on congested road segments -- Timed traffic lights should now correctly detect trains and trams -- Bugfix: GUI: Junction restriction icons sometimes disappear -- Updated Chinese (simplified) translation - -### 1.7.11, 01/09/2016 -- Updated to game version 1.5.1-f3 - -### 1.7.10, 31/08/2016 -- Players can now disable spawning of pocket cars -- Updated Chinese (simplified) translation -- Bugfix: Timed traffic lights were flickering -- Bugfix: Pedestrian traffic lights were not working as expected -- Bugfix: When upgrading/removing/adding a road segment, nearby junction restrictions were removed -- Bugfix: Setting up vehicle restrictions affects trams (thanks to @chem for reporting) -- Bugfix: Manual pedestrian traffic light states were not correctly handled -- Bugfix: Junction restrictions overlay did not show all restricted junctions - -### 1.7.9, 22/08/2016 -- In-game traffic light states are now correctly rendered when showing "yellow" -- Removed negative effects on public transport usage -- GUI: Traffic light states do not flicker anymore -- Performance improvements - -### 1.7.8, 18/08/2016: -- Bugfix: Cims sometimes got stuck (thanks to all reports and especially to @Thilawyn for providing a savegame) -- GUI: Improved traffic light arrow display -- Improved performance while saving - -### 1.7.7, 16/08/2016: -- AI: Instead of walking long distances, citizens now use a car -- AI: Citizens will remember their last used mode of transport (e.g. they will not drive to work and come return by bus anymore) -- AI: Increased path-finding costs for traversing over restricted road segments -- Added "110" speed limit -- GUI: Windows are draggable -- GUI: Improved window scaling on lower resolutions -- Improved performance while saving - -### 1.7.6, 14/08/2016: -- New feature: Players may now prohibit cims from crossing the street -- AI: Tuned randomization of lane changing behavior -- AI: Introduced path-finding costs for leaving main highway (should reduce amount of detours taken) -- UI: Clicking with the secondary mouse button now deselects the currently selected node/segment for all tools -- Added the possibility to connect train track lanes with the lane connector (as requested by @pilot.patrick93) -- Moved options from "Change lane arrows" to "Vehicle restrictions" tool -- Updated Russian translation -- Bugfix: AI: At specific junctions, vehicles were not obeying lane connections correctly (thanks to @Mariobro14 for pointing out this problem) -- Bugfix: AI: Path-finding costs for u-turns were not correctly calculated (thanks to @Mariobro14 for pointing out this problem) -- Bugfix: Vehicles were endlessly waiting for each other at junctions with certain priority sign configurations -- Bugfix: AI: Lane changing costs corrected - -### 1.7.5, 07/08/2016: -- Bugfix: AI: Cims were using pocket cars whenever possible -- Bugfix: AI: Path-finding failures led to much less vehicles spawning -- Bugfix: AI: Lane selection at junctions with custom lane connection was not always working properly (e.g. for Network Extensions roads with middle lane) -- Bugfix: While editing a timed traffic light it could happen that the traffic light was deleted - -### 1.7.4, 31/07/2016: -- AI: Switched from relative to absolute traffic density measurement -- AI: Tuned new parameters -- Bugfix: Activated/Disabled features were not loaded correctly -- Bugfix: AI: At specific junctions the lane changer did not work as intended -- Possible fix for OSX performance issues -- Code improvements -- Added French translations (thanks to @simon.royer007 for translating!) - -### 1.7.3, 29/07/2016: -- Added the ability to enable/disable mod features (e.g. for performance reasons) -- Bugfix: Vehicle type determination was incorrect (fixed u-turning trams/trains, stuck vehicles) -- Bugfix: Clicking on a train/tram node with the lane connector tool led to an uncorrectable error (thanks to @noaccount for reporting this problem) -- Further code improvements - -### 1.7.2, 26/07/2016: -- Optimized UI overlay performance - -### 1.7.1, 24/07/2016: -- Reverted "Busses now may only ignore lane arrows if driving on a bus lane" for now -- Bugfix: Trains were not despawning if no path could be calculated -- Workaround for third-party issue: TM:PE now detects if the calculation of total vehicle length fails - -### 1.7.0, 23/07/2016: -- New feature: Traffic++ lane connector -- Busses now may only ignore lane arrows if driving on a bus lane -- Rewritten and simplified vehicle position tracking near timed traffic lights and priority signs for performance reasons -- Improved performance of priority sign rules -- AI: Cims now ignore junctions where pedestrian lights never change to green -- AI: Removed the need to define a lane changing probability -- AI: Tweaked lane changing parameters -- AI: Highway rules are automatically disabled at complex junctions (= more than 1 incoming and more than 1 outgoing roads) -- Improved UI performance if overlays are deactivated -- Simulation accuracy now also controls time intervals between traffic measurements -- Added compatibility detection for the Rainfall mod -- Improved fault-tolerance of the load/save system -- Default wait-flow balance is set to 0.8 -- Bugfix: Taxis were allowed to ignore lane arrows -- Bugfix: AI: Highway rules on left-hand traffic maps did not work the same as on right-hand traffic maps -- Bugfix: Upgrading a road segment next to a timed traffic light removed the traffic light leading to an inconsistent state (thanks to @ad.vissers for pointing out this problem) - -### 1.6.22, 29/06/2016: -- AI: Taxis now may not ignore lane arrows and are using bus lanes whenever possible (thanks to @Cochy for pointing out this issue) -- AI: Busses may only ignore lane arrows while driving on a bus lane -- Bugfix: Traffic measurement at timed traffic lights was incorrect - -### 1.6.22, 21/06/2016: -- Speed/vehicle restrictions may now be applied to all road segments between two junctions by holding the shift key -- Reworked how changes in the road network are recognized -- Advanced Vehicle AI: Improved lane selection at junctions where bus lanes end -- Advanced Vehicle AI: Improved lane selection of busses -- Improved automatic pedestrian lights -- Improved separate traffic lights: Traffic lights now control traffic lane-wise -- UI: Sensitivity slider is only available while adding/editing a step or while in test mode -- Bugfix: Lane selection on maps with left-hand traffic was incorrect -- Bugfix: While building in pause mode, changes in the road network were not always recognized causing vehicles to stop/despawn -- Bugfix: Police cars off-duty were ignoring lane arrows -- Bugfix: If public transport stops were near a junction, trams/busses were not counted by timed traffic lights (many thanks to Filip for identifying this problem) -- Bugfix: Trains/Trams were sometimes ignoring timed traffic lights (many thanks to Filip for identifying this problem) -- Bugfix: Building roads with bus lanes caused garbage, bodies, etc. to pile up - -### 1.6.21, 14/06/2016: -- Bugfix: Too few cargo trains were spawning (thanks to @Scratch, @toruk_makto1, @Mr.Miyagi, @mottoh and @Syparo for pointing out this problem) -- Bugfix: Vehicle restrictions did not work as expected (thanks to @nordlaser for pointing out this problem) - -### 1.6.20, 11/06/2016: -- Bugfix: Priority signs were not working correctly (thanks to @mottoth, @Madgemade for pointing out this problem) - -### 1.6.19, 11/06/2016 -- Bugfix: Timed traffic lights UI not working as expected (thanks to @Madgemade for pointing out this problem) - -### 1.6.18, 09/06/2016 -- Updated for game patch 1.5.0-f4 -- Improved performance of priority signs and timed traffic lights -- Players can now select elevated rail segments/nodes -- Trams and trains now follow priority signs -- Improved UI behavior when setting up priority signs - -### 1.6.17, 20/04/2016 -- Hotfix for reported path-finding problems - -### 1.6.16, 19/04/2016 -- Updated for game patch 1.4.1-f2 - -### 1.6.15, 22/03/2016 -- Updated for game path 1.4.0-f3 -- Possible fix for crashes described by @cosminel1982 -- Added traditional Chinese translation - -### 1.6.14, 17/03/2016 -- Bugfix: Cargo trucks did not obey vehicle restrictions (thanks to @ad.vissers for pointing out this problem) -- Bugfix: When Advanced AI was deactivated, u-turns did not have costs assigned - -### 1.6.13, 16/03/2016 -- Added Dutch translation -- The pedestrian light mode of a traffic light can now be switched back to automatic -- Vehicles approaching a different speed limit change their speed more gradually -- The size of signs and symbols in the overlay is determined by screen resolution height, not by width -- Path-finding: Performance improvements -- Path-finding: Fine-tuned lane changing behaviour -- Bugfix: After loading another savegame, timed traffic lights stopped working for a certain time -- Bugfix: Lane speed calculation corrected - -### 1.6.12, 03/03/2016 -- Improved memory usage -- Bugfix: Adding/removing junctions to/from existing timed traffic lights did not work (thanks to @nieksen for pointing out this problem) -- Bugfix: Separate timed traffic lights were sometimes not saved (thanks to @nieksen for pointing out this problem) -- Bugfix: Fixed an initialization error (thanks to @GordonDry for pointing out this problem) - -### 1.6.11, 03/03/2016 -- Added Chinese translation -- By pressing "Page up"/"Page down" you can now switch between traffic and default map view -- Size of information icons and signs is now based on your screen resolution -- UI code refactored - -### 1.6.10, 02/03/2016 -- Additional controls for vehicle restrictions added -- Bugfix: Clicking on a Traffic Manager overlay resulted in vanilla game components (e.g. houses, vehicles) being activated - -### 1.6.9, 02/03/2016 -- Updated for game patch 1.3.2-f1 - -### 1.6.8, 01/03/2016 -- Path-finding: Major performance improvements -- Updated Japanese translation (thanks to @Akira Ishizaki for translating!) -- Added Spanish translation - -### 1.6.7, 27/02/2016 -- Tuned AI parameters -- Improved traffic density measurements -- Improved lane changing near junctions: Reintroduced costs for lane changing before junctions -- Improved vehicle behavior near blocked roads (e.g. while a building is burning) -- Bugfix: Automatic pedestrian lights for outgoing one-ways fixed -- Bugfix: U-turns did not have appropriate costs assigned -- Bugfix: The time span between AI traffic measurements was too high - -### 1.6.6, 27/02/2016 -- It should now be easier to select segment ends in order to change lane arrows. -- Priority signs now cannot be setup at outgoing one-ways. -- Updated French translation (thanks to @simon.royer007 for translating!) -- Updated Polish translation (thanks to @Krzychu1245 for translating!) -- Updated Portuguese translation (thanks to @igordeeoliveira for translating!) -- Updated Russian translation (thanks to @FireGames for translating!) -- Bugfix: U-turning vehicles were not obeying the correct directional traffic light (thanks to @t1a2l for pointing out this problem) - -### 1.6.5, 24/02/2016 -- Added despawning setting to options dialog -- Improved detection of Traffic++ V2 - -### 1.6.4, 23/02/2016 -- Minor performance improvements -- Bugfix: Path-finding calculated erroneous traffic density values -- Bugfix: Cims left the bus just to hop on a bus of the same line again (thanks to @kamzik911 for pointing out this problem) -- Bugfix: Despawn control did not work (thanks to @xXHistoricalxDemoXx for pointing out this problem) -- Bugfix: State of new settings was not displayed corretly (thanks to @Lord_Assaultーさま for pointing out this problem) -- Bugfix: Default settings for vehicle restrictions on bus lanes corrected -- Bugfix: Pedestrian lights at railway junctions fixed (they are still invisible but are derived from the car traffic light state automatically) - -### 1.6.3, 22/02/2016 -- Bugfix: Using the "Old Town" policy led to vehicles not spawning. -- Bugfix: Planes, cargo trains and ship were sometimes not arriving -- Bugfix: Trams are not doing u-turns anymore - -### 1.6.2, 20/02/2016 -- Trams are now obeying speed limits (thanks to @Clausewitz for pointing out the issue) -- Bugfix: Clear traffic sometimes throwed an error -- Bugfix: Vehicle restrctions did not work as expected (thanks to @[Delta ²k5] for pointing out this problem) -- Bugfix: Transition of automatic pedestrian lights fixed - -### 1.6.1, 20/02/2016 -- Improved performance -- Bugfix: Fixed UI issues -- Modifying mod options through the main menu now gives an annoying warning message instead of a blank page. - -### 1.6.0, 18/02/2016 -- New feature: Separate traffic lights for different vehicle types -- New feature: Vehicle restrictions -- Snowfall compatibility -- Better handling of vehicle bans -- Improved the method for calculating lane traffic densities -- Ambulances, fire trucks and police cars on duty are now ignoring lane arrows -- Timed traffic lights may now be setup at arbitrary nodes on railway tracks -- Reckless drivers now do not enter railroad crossings if the barrier is down -- Option dialog is disabled if accessed through the main menu -- Performance optimizations -- Advanced Vehicle AI: Improved lane spreading -- The option "Vehicles may enter blocked junctions" may now be defined for each junction separately -- Vehicles going straight may now change lanes at junctions -- Vehicles may now perform u-turns at junctions that have an appropriate lane arrow configuration -- Road conditions (snow, maintenance state) may now have a higher impact on vehicle speed (see "Options" menu) -- Emergency vehicles on duty now always aim for the the fastest route -- Bugfix: Path-finding costs for crossing a junction fixed -- Bugfix: Vehicle detection at timed traffic lights did not work as expected -- Bugfix: Not all valid traffic light arrow modes were reachable - -### 1.5.2, 01/02/2016 -- Traffic lights may now be added to/removed from underground junctions -- Traffic lights may now be setup at *some* points of railway tracks (there seems to be a game-internal bug that prevents selecting arbitrary railway nodes) -- Display of priority signs, speed limits and timed traffic lights may now be toggled via the options dialog -- Bugfix: Reckless driving does not apply for trains (thanks to @GordonDry for pointing out this problem) -- Bugfix: Manual traffic lights were not working (thanks to @Mas71 for pointing out this problem) -- Bugfix: Pedestrians were ignoring timed traffic lights (thanks to @Hannes8910 for pointing out this problem) -- Bugfix: Sometimes speed limits were not saved (thanks to @cca_mikeman for pointing out this problem) - -### 1.5.1, 31/01/2016 -- Trains are now following speed limits - -### 1.5.0, 30/01/2016 -- New feature: Speed restrictions (as requested by @Gfurst) -- AI: Parameters tuned -- Code improvements -- Lane arrow changer window is now positioned near the edited junction (as requested by @GordonDry) -- Bugfix: Flowing/Waiting vehicles count corrected - -### 1.4.9, 27/01/2016 -- Junctions may now be added to/removed from timed traffic lights after they are created -- When viewing/moving a timed step, the displayed/moved step is now highlighted (thanks to Joe for this idea) -- Performance improvements -- Bugfix (AI): Fixed a division by zero error (thanks to @GordonDry for pointing out this problem) -- Bugfix (AI): Near highway exits vehicles tended to use the outermost lane (thanks to @Zake for pointing out this problem) -- Bugfix: Some lane arrows disappeared on maps using left-hand traffic systems (thanks to @Mas71 for pointing out this problem) -- Bugfix: In lane arrow edit mode, the order of arrows was sometimes incorrect (thanks to @Glowstrontium for pointing out this problem) -- Bugfix: Lane merging in left-hand traffic systems fixed -- Bugfix: Turning priority roads fixed (thanks to @GordonDry for pointing out this problem) - -### 1.4.8, 25/01/2016 -- AI: Parameters have been tuned -- AI: Added traffic density measurements -- Performance improvements -- Added translation to Polish (thanks to @Krzychu1245 for working on this!) -- Added translation to Russian (thanks to @FireGames for working on this!) -- Bugfix: After removing a timed or manual light the traffic light was deleted (thanks to @Mas71 for pointing out this problem) -- Bugfix: Segment geometries were not always calculated -- Bugfix: In highway rule mode, lane arrows sometimes flickered -- Bugfix: Some traffic light arrows were sometimes not selectable - -### 1.4.7, 22/01/2016 -- Added translation to Portuguese (thanks to @igordeeoliveira for working on this!) -- Reduced mean size of files can become quite big (thanks to @GordonDry for reporting this problem) -- Bugfix: Freight ships/trains were not coming in (thanks to @Mas71 and @clus for reporting this problem) -- Bugfix: The toggle "Vehicles may enter blocked junctions" did not work properly (thanks for @exxonic for reporting this problem) -- Bugfix: If a timed traffic light is being edited the segment geometry information is not updated (thanks to @GordonDry for reporting this problem) - -### 1.4.6, 22/01/2016 -- Running average lane speeds are measured now -- Minor fixes - -### 1.4.5, 22/01/2016 -- The option "Vehicles may enter blocked junctions" may now be defined for each junction separately -- Bugfix: A deadlock in the path-finding is fixed -- Bugfix: Small timed light sensitivity values (< 0.1) were not saved correctly -- Bugfix: Timed traffic lights were not working for some players -- Refactored segment geometry calculation - -### 1.4.4, 21/01/2016 -- Added localization support - -### 1.4.3, 20/01/2016 -- Several performance improvements -- Improved calculation of segment geometries -- Improved load balancing -- Police cars, ambulances, fire trucks and hearses are now also controlled by the AI -- Bugfix: Vehicles did not always take the shortest path -- Bugfix: Vehicles disappeared after deleting/upgrading a road segment -- Bugfix: Fixed an error in path-finding cost calculation -- Bugfix: Outgoing roads were treated as ingoing roads when highway rules were activated - -### 1.4.2, 16/01/2016 -- Several major performance improvements (thanks to @sci302 for pointing out those issues) -- Improved the way traffic lights are saved/loaded -- Lane-wise traffic density is only measured if Advanced AI is activated -- Bugfix: AI did not consider speed limits/road types during path calculation (thanks to @bhanhart, @sa62039 for pointing out this problem) -- Connecting a city road to a highway road that does not supply enough lanes for merging leads to behavior people do not understand (see manual). Option added to disable highway rules. -- Bugfix: Vehicles were stopping in front of green traffic lights -- Bugfix: Stop/Yield signs were not working properly (thanks to @GordonDry, @Glowstrontium for pointing out this problem) -- Bugfix: Cargo trucks were ignoring the "Heavy ban" policy, they should do now (thanks to @Scratch for pointing out this problem) - -### 1.4.1, 15/01/2016 -- Bugfix: Path-finding near junctions fixed - -### 1.4.0, 15/01/2016 -- Introducing Advanced Vehicle AI (disabled by default! Go to "Options" and enable it if you want to use it.) -- Bugfix: Traffic lights were popping up in the middle of roads -- Bugfix: Fixed the lane changer for left-hand traffic systems (thanks to @Phishie for pointing out this problem) -- Bugfix: Traffic lights on invalid nodes are not saved anymore - -### 1.3.24, 13/01/2016 -- Improved handling of priority signs -- Priority signs: After adding two main road signs the next offered sign is a yield sign -- Priority signs: Vehicles now should notice earlier that they can enter a junction -- Removed the legacy XML file save system -- Invalid (not created) lanes are not saved/loaded anymore -- Added a configuration option that allows vehicles to enter blocked junctions -- Bugfix: Some priority signs were not saved -- Bugfix: Priority signs on deleted segments are now deleted too -- Bugfix: Lane arrows on removed lanes are now removed too -- Bugfix: Adding a priority sign to a junction having more than one main sign creates a yield sign (thanks to @GordonDry for pointing out this problem) -- Bugfix: If reckless driving was set to "The Holy City (0 %)", vehicles blocked intersections with traffic light. -- Bugfix: Traffic light arrow modes were sometimes not correctly saved - -### 1.3.23, 09/01/2016 -- Bugfix: Corrected an issue where toggled traffic lights would not be saved/loaded correctly (thanks to @Jeffrios and @AOD_War_2g for pointing out this problem) -- Option added to forget all toggled traffic lights - -### 1.3.22, 08/01/2016 -- Added an option allowing busses to ignore lane arrows -- Added an option to display nodes and segments - -### 1.3.21, 06/01/2016 -- New feature: Traffic Sensitivity Tuning -- UI improvements: When adding a new step to a timed traffic light the lights are inverted. -- Timed traffic light status symbols should now be less annoying -- Bugfix: Deletion of junctions that were members of a traffic light group is now handled correctly - -### 1.3.20, 04/01/2016 -- Bugfix: Timed traffic lights are not saved correctly after upgrading a road nearby -- UI improvements -- New feature: Reckless driving - -### 1.3.19, 04/01/2016 -- Timed traffic lights: Absolute minimum time changed to 1 -- Timed traffic lights: Velocity of vehicles is being measured to detect traffic jams -- Improved traffic flow measurement -- Improved path finding: Cims may now choose their lanes more independently -- Bugfix: Upgrading a road resets the traffic light arrow mode - -### 1.3.18, 03/01/2016 -- Provided a fix for unconnected junctions caused by other mods -- Crosswalk feature removed. If you need to add/remove crosswalks please use the "Crossings" mod. -- UI improvements: You can now switch between activated timed traffic lights without clicking on the menu button again - -### 1.3.17, 03/01/2016 -- Bugfix: Timed traffic lights cannot be added again after removal, toggling traffic lights does not work (thanks to @Fabrice, @ChakyHH, @sensual.heathen for pointing out this problem) -- Bugfix: After using the "Manual traffic lights" option, toggling lights does not work (thanks to @Timso113 for pointing out this problem) - -### 1.3.16, 03/01/2016 -- Bugfix: Traffic light settings on roads of the Network Extensions mods are not saved (thanks to @Scarface, @martintech and @Sonic for pointing out this problem) -- Improved save data management - -### 1.3.15, 02/01/2016 -- Simulation accuracy (and thus performance) is now controllable through the game options dialog -- Bugfix: Vehicles on a priority road sometimes stop without an obvious reason - -### 1.3.14, 01/01/2016 -- Improved performance -- UI: Non-timed traffic lights are now automatically removed when adding priority signs to a junction -- Adjusted the adaptive traffic light decision formula (vehicle lengths are considered now) -- Traffic two road segments in front of a timed traffic light is being measured now - -### 1.3.13, 01/01/2016 -- Bugfix: Lane arrows are not correctly translated into path finding decisions (thanks to @bvoice360 for pointing out this problem) -- Bugfix: Priority signs are sometimes undeletable (thank to @Blackwolf for pointing out this problem) -- Bugfix: Errors occur when other mods without namespace definitions are loaded (thanks to @Arch Angel for pointing out this problem) -- Connecting a new road segment to a junction that already has priority signs now allows modification of the new priority sign - -### 1.3.12, 30/12/2015 -- Bugfix: Priority signs are not editable (thanks to @ningcaohan for pointing out this problem) - -### 1.3.11, 30/12/2015 -- Road segments next to a timed traffic light may now be deleted/upgraded/added without leading to deletion of the light -- Priority signs and Timed traffic light state symbols are now visible as soon as the menu is opened - -### 1.3.10, 29/12/2015 -- Fixed an issue where timed traffic light groups were not deleted after deleting an adjacent segment - -### 1.3.9, 29/12/2015 -- Introduced information icons for timed traffic lights -- Mod is now compatible with "Improved AI" (Lane changer is deactivated if "Improved AI" is active) - -### 1.3.8, 29/12/2015 -- Articulated busses are now simulated correctly (thanks to @nieksen for pointing out this problem) -- UI improvements - -### 1.3.7, 28/12/2015 -- When setting up a new timed traffic light, yellow lights from the real-world state are not taken over -- When loading another save game via the escape menu, Traffic Manager does not crash -- When loading another save game via the escape menu, Traffic++ detection works as intended -- Lane arrows are saved correctly - -### 1.3.6, 28/12/2015 -- Bugfix: wrong flow value taken when comparing flowing vehicles -- Forced node rendering after modifying a crosswalk - -### 1.3.5, 28/12/2015 -- Fixed pedestrian traffic Lights (thanks to @Glowstrontium for pointing out this problem) -- Better fix for: Deleting a segment with a timed traffic light does not cause a NullReferenceException -- Adjusted the comparison between flowing (green light) and waiting (red light) traffic - -### 1.3.4, 27/12/2015 -- Better traffic jam handling - -### 1.3.3, 27/12/2015 -- (Temporary) hotfix: Deleting a segment with a timed traffic light does not cause a NullReferenceException -- If priority signs are located behind the camera they are not rendered anymore - -### 1.3.2, 27/12/2015 -- Priority signs are persistently visible when Traffic Manager is in "Add priority sign" mode -- Synchronized traffic light rendering: In-game Traffic lights display the correct color (Thanks to @Fabrice for pointing out this problem) -- Traffic lights switch between green, yellow and red. Not only between green and red. -- UI tool tips are more explanatory and are shown longer. - -### 1.3.1, 26/12/2015 -- Minimum time units may be zero now -- Timed traffic lights of deleted/modified junctions get properly disposed - -### 1.3.0, 25/12/2015 -- **Adaptive Timed Traffic Lights** (automatically adjusted based on traffic amount) - -### 1.2.0 (iMarbot) -- Updated for 1.2.2-f2 game patch. +- Fixed: Parking AI: Transport mode storage causes performance issues during loading (#147, thanks to hannebambel002 and oneeyets for reporting and further for providing logs and savegames) + +### TM:PE 1.10.6, 24/05/2018 + +- Added: Lane connector can be used on monorail tracks +- Added: Mod option - Main menu size can be controlled +- Added: Mod option - GUI and overlay transparency can be controlled +- Added: Mod option - Penalties for switching between different public transport lines can be toggled +- Added: Cims can now be removed from the game +- Improved: Advanced Vehicle AI - Tuned parameters +- Improved: Randomization for realistic speeds such that vehicles may change their target velocity over time +- Improved: Vehicle position tracking +- Improved: Mod compatibility checks +- Improved: Parking AI - Improved behaviour in situations where vehicles are parked near public transport hubs and road connections are partially unavailable +- Improved: Window design +- Fixed: Parking AI - Not all possible paths are regarded during path-finding +- Fixed: Parking AI - Cims become confused when trying to return their abandoned car back home (special thanks to Wildcard-25 for reporting and solving this issue) +- Fixed: Parking AI - Cims do not search for parking building when road-side parking spaces are found +- Fixed: Parking AI - Parked vehicles are spawned near the source building even when cims are already en route +- Fixed: Parking AI - Cims sometimes get stuck in an infinite loop while trying to enter their parked car +- Fixed: Lane connector does not work for roads with more than ten lanes +- Fixed: Allowing/Disallowing vehicles to enter a blocked junction does not work for certain junctions +- Updated: Compatible with C:SL 1.9.2-f1 +- Updated: Compatible with C:SL 1.9.3-f1 +- Updated: Compatible with C:SL 1.10.0-f3 +- Updated: Dynamic Lane Selection: Absolute speed measurements are used instead of relative measurements +- Updated: Service vehicles now allowed to ignore lane arrows when leaving their source building; better for dead-end roads with median +- Updated: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) + +### C:SL 1.10.0-f3 (Park Life), 24/05/2018 + +- Added: Park maintenance service and vehicle +- Added: Walking tours +- Added: Sightseeing bus tours and depot +- Added: Hot air balloons +- Fixed: Confused ships rotating forever +- Fixed: All traffic use Bus & Taxi lane when Old Town policy is active +- Fixed: Lots of other game and DLC bugs +- Updated: Trees reduce noise pollution + +### C:SL 1.9.3-f1, 23/03/2018 + +- Fixed: Bugs caused by prior patch + +### C:SL 1.9.2-f1, 09/03/2018 + +- Fixed: Minor bugs + +### TM:PE 1.10.5, 06/01/2018 + +- Added: Randomization for lane changing costs +- Added: Randomization for "trucks prefer innermost lanes on highways" costs +- Added: path-finding costs for public transport transitions +- Fixed: Main menu button might be out of view +- Fixed: Division by zero occurs for low speed roads +- Fixed: Automatic pedestrian lights at railroad do not work as expected +- Fixed: Timed traffic lights show up for bicycles (they should not) +- Fixed: Due to a multi-threading issue junction restrictions may cause the game state to become inconsistent +- Fixed: Routing rules prevents vehicles from spawning when starting building lies too close to an intersection/road end +- Fixed: Disabling tutorial message has no effect +- Fixed: "Stay on lane" feature does not work as intended for certain nodes +- Updated: Compatible with C:SL 1.9.1 +- Updated: Busses are allowed to switch multiple lanes after leaving a bus stop +- Updated: Pedestrian traffic lights do not show up if crossing the street is prohibited +- Updated: Simplified Chinese translation updated (thanks to Emphasia for translating) +- Updated: Polish translation updated (thanks to Krzychu1245 for translating) +- Removed: Unnecessary calculations in path-finding +- Removed: UI scaling + +### C:SL 1.9.1, 05/12/2017 + +- Fixed: Various game bugs +- Updated: Updated Unity to 5.6.4p2 + +### TM:PE 1.10.4, 19/10/2017 + +- Added: Possibility to add priority signs at multiple junctions at once (press Shift) +- Added: Tutorials (can be disabled in the options window globally) +- Updated: Compatible with C:SL 1.9.0-f5 + +### C:SL 1.9.0-f5 (Green Cities), 19/10/2017 + +- Added: Biofuel busses and recycling trucks +- Added: Electric cars and parking spaces with chargers +- Fixed: Huge number of game and localisation bugs +- Updated: Noise Pollution overhaul +- Updated: Train track intersection rules +- Updated: Unity version has been updated to 5.6.3p4 + +### TM:PE 1.10.3, 18/08/2017 + +- Fixed: Setting unlimited speed limit causes vehicles to crawl at low speed (thanks to sethisuwan for reporting this issue) +- Fixed: Vehicle-separated traffic lights do not show up for trams & monorails (thanks to thecitiesdork for reporting this issue) + +### TM:PE 1.10.2, 17/08/2017 + +- Improved: performance +- Fixed: Pedestrians sometimes ignore red traffic light signals (thanks to (c)RIKUPI™ for reporting this issue) +- Fixed: Timed traffic lights do not correctly recognize set vehicle restrictions (thanks to alborzka for reporting this issue) +- Updated: Compatible with C:SL 1.8.0-f3 + +### C:SL 1.8.0-f3 (Concerts), 17/08/2017 + +- Added: Festival areas +- Fixed: Pathfinder causing problems with multiple policies +- Fixed: Traffic routes info view does not show bicycles for players who don't own After Dark +- Fixed: Ships can travel on land and through dam +- Fixed: Cargo Train Terminal not working when build next to road with bicycle lanes +- Fixed: Large number of other game bugs + +### TM:PE 1.10.1, 05/08/2017 + +- Fixed: Default routing is disabled if the lane connector is used on a subset of all available lanes only +- Fixed: Parking AI cannot be enabled/disabled +- Fixed: Lane connection points can connected to themselves +- Updated: Polish, Korean, and Simplified Chinese translations + +### TM:PE 1.10.0, 30/07/2017 + +- Added: Dynamic Lane Selection +- Added: Adaptive step switching +- Added: Individual vehicles may be removed from the game +- Added: Mod option - Vehicle restrictions aggression +- Added: Mod option - Vehicles follow priority rules at junctions with timed traffic lights +- Added: Path-find statistics label +- Added: Confirmation dialog for "Clear Traffic" button +- Improved: Path-finding performance +- Improved: Traffic measurement engine performance +- Improved: Currently active timed traffic light step is remembered +- Improved: Disabling the Parking AI triggers graceful clean up procedure +- Improved: Vehicle state tracking +- Improved: Parking AI - Vehicles can now find parking spaces at the opposite road side +- Improved: Parking AI - Included an improved fallback logic for some edge cases +- Improved: Parking AI - Citizens should now be more successful in returning their cars back home +- Improved: Parking AI - Tuned parking radius parameters +- Improved: Parking AI - If the limit for parked vehicles is reached and parking fails due to it, no alternative parking space is queried +- Improved: Vehicle AI - Busses prefer lanes with correct lane arrow over incorrect ones +- Fixed: Workaround for a base game issue that causes trams to get stuck +- Fixed: Using the bulldozer tool might lead to inconsistent road geometry information +- Fixed: Citizens that fail to approach their parked car fly towards their target building +- Fixed: Parking AI: Path-finding fails if cars are parked too far away from a road +- Fixed: Parking AI: Citizens approaching a car start to float away +- Fixed: "Heavy vehicles prefer outer lanes on highways" does not work +- Fixed: The lane connector does not allow connecting all available lane end points at train stations and on bidirectional one-lane train tracks +- Fixed: Vehicles may get stuck in several situations +- Updated: Compatible with C:SL 1.7.2-f1 +- Updated: Upgrading to a road with bus lanes now copies an already existing traffic light state to the new traffic light +- Updated: Adding a vehicle separate light to a timed traffic lights applies the main light configuration +- Updated: Vehicles use queue skipping to prioritize path-finding runs that are caused by road modifications +- Updated: Trains do not longer stop in front of green timed traffic lights +- Updated: Relocated some mod options +- Updated: It is now possible to connect train station tracks and outside connections with the lane connector +- Updated: Trains do not wait for each other anymore near timed traffic lights +- Updated: The option "Road condition has a bigger impact on vehicle speed" is only shown if the Snowfall DLC is owned +- Updated: Reorganized global configuration file (sorry, your main menu and main button positions are reset) +- Updated: The flow/wait calculation mode to be used is now configurable via the global configuration file + +### C:SL 1.7.2-f1, 01/06/2017 + +- Fixed: Multiple public transport stops at the same location causing division by zero / crashing the UI +- Fixed: Double clicking creating multiple stops at the same place +- Fixed: Various other bugs + +### TM:PE 1.9.6, 28/05/2017 + +- Fixed: Vehicles cannot perform u-turns at junctions with only one outgoing segment (thanks to Sunbird for reporting this issue) +- Fixed: Path-finding costs for large distances exceed the maximum allowed value (thanks to Huitsi for reporting this issue) +- Fixed: Under certain circumstances path-finding at railroad crossings allow switching from road to rail tracks. +- Updated: Simplified Chinese translation + +### TM:PE 1.9.5, 24/05/2017 + +- Improved: Language can now be switched without requiring a game restart +- Fixed: Routing calculation does not work as expected for one-way roads with tram tracks (thanks to bigblade66, Battelman2 and AS_ for reporting and providing extensive information) +- Fixed: Copying timed traffic lights causes timed traffic lights to be omitted during the save process (thanks to jakeroot and t1a2l for reporting this issue) +- Fixed: In certain situations unnecessary vehicle-separate traffic lights are being created +- Fixed: Upgrading a train track segment next to a timed traffic light causes trains to ignore the traffic light +- Fixed: Hotfix - Cable cars despawn at end-of-line stations +- Updated: Compatible with C:SL 1.7.1-f1 +- Updated: Polish, Korean and Italian translation + +### TM:PE 1.9.4, 23/05/2017 + +- Added: Mod option - Ban private cars and trucks on bus lanes +- Improved: Optimized path-finding +- Fixed: Path-finding is unable to calculate certain paths after modifying the road network +- Updated: Increased path-finding cost for private cars driving on bus lanes +- Updated: Increased path-finding cost for disregarding vehicle restrictions +- Updated: Spanish and French translation + +### C:SL 1.7.1-f1, 23/05/2017 + +- Fixed: Minor bugs + +### TM:PE 1.9.3, 22/05/2017 + +- Improved: Modified junction restrictions come into effect instantaneously +- Fixed: AI: Segment traffic data is not taken into account +- Fixed: Priority rules are not properly obeyed +- Fixed: Under certain circumstances priority signs cannot be removed +- Fixed: Path-finding is unable to calculate certain paths +- Updated: UI - Saving a timed step does not reset the timed traffic light to the first state +- Removed: Default vehicle restrictions from bus lanes +- Removed: Disabled notification of route recalculating because some players report crashes + +### TM:PE 1.9.2, 20/05/2017 + +- Improved: UI - Main menu & UI tools performance improved +- Fixed: Traffic lights can be removed from junctions that are controlled by a timed traffic light program + +### TM:PE 1.9.1, 19/05/2017 + +- Fixed: Using the vanilla traffic light toggling feature crashes the game if TMPE's main menu has not been opened at least once +- Fixed: AI - More car traffic and less public transportation present than in vanilla +- Updated: French, Dutch and Korean translation + +### TM:PE 1.9.0, 18/05/2017 + +- Added: Parking restrictions +- Added: Speed limits can be set up for individual lanes with the Control key +- Added: Added timed traffic light and speed limit support for monorails +- Added: Copy & paste for individual timed traffic lights +- Added: Rotate individual timed traffic lights +- Added: Lane customizations may come into effect instantaneously +- Added: Mod option - Main button position can be locked +- Added: Mod option - Main menu position can be locked +- Added: Mod option - Added language selection in options dialog +- Added: Mod option - Customization of lane arrows, lane connections and vehicle restrictions can now come into effect instantaneously +- Added: Support for custom languages +- Added: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) +- Improved: Performance improvements +- Improved: Advanced Vehicle AI - Algorithm updated, performance improved - Possible routing decisions are now being pre-calculated +- Improved: AI - Tuned path-finding parameters +- Fixed: Cars sometimes get stuck forever when the Advanced Parking AI is activated (thanks to cmfcmf for reporting this issue) +- Fixed: Busses do not perform u-turns even if the transport line show u-turns (thanks to dymanoid for reporting this issue) +- Fixed: Timed traffic lights do not work as expected on single-direction train tracks (thanks to DaEgi01 for reporting this issue) +- Fixed: Vehicle restriction and speed limit signs overlay is displayed on the wrong side of inverted road segments +- Fixed: Influx statistics value is zero (thanks to hjo for reporting this issue) +- Updated: Compatible with C:SL 1.7.0-f5 +- Updated: Major code refactorings +- Updated: UI - More compact, movable main menu UI +- Updated: translations: German, Polish, Russian, Portuguese, Traditional Chinese +- Updated: Path-finding cost multiplicator for vehicle restrictions is now configurable in TMPE_GlobalConfig.xml +- Updated: Unified traffic light toggling feature with game code +- Updated: Reworked the way that traffic measurements are performed + +### C:SL 1.7.0-f5 (Mass Transit), 18/05/2017 + +- Added: Ferries +- Added: Cable Cars +- Added: Elevated monorail +- Added: Blimps +- Added: New stations, transport hubs and service buildings +- Added: Named routes +- Added: One-way train tracks +- Added: Emergency vehicles choose free lane if available, otherwise lane with least traffic +- Added: More public transport info views +- Added: Choose if rail stations accept intercity traffic +- Added: Stop signs at intersections +- Added: Toggle traffic lights at intersections +- Added: Vehicles have show / hide routes button +- Added: Automatic public transport vehicle unbunching +- Fixed: Train tracks could be updated after disaster by upgrading +- Fixed: Helicopters still won't visit buildings with no road connection +- Fixed: Lots of errors, particularly localisation +- Updated: Increased emergency vehicles speed +- Updated: Smaller outside connection capacity for smaller roads +- Updated: Upgraded to Unity 5.5.3f1 + +### TM:PE 1.8.16, 20/03/2017 + +- Improved: lane selection for busses if the option "Busses may ignore lane arrows" is activated +- Fixed: The game sometimes freezes when using the timed traffic light tool +- Fixed: Lane connections are not correctly removed after modifying/removing a junction +- Fixed: Selecting a junction for setting up junction restrictions toggles the currently hovered junction restriction icon +- Updated: Lane connections can now also be removed by pressing the backspace key + +### TM:PE 1.8.15, 27/01/2017 + +- Updated: Compatible with C:SL 1.6.3-f1 + +### C:SL 1.6.3-f1, 26/01/2017 + +- Fixed: Helicopter not used if building has no road connection +- Fixed: Various other game bugs + +### TM:PE 1.8.14, 07/01/2017 + +- Added: Tram lanes can now be customized by using the lane connector tool +- Improved: Minor performance optimizations for priority sign simulation +- Fixed: Wait/flow ratio at timed traffic lights is sometimes not correctly calculated +- Fixed: A deadlock situation can arise at junctions with priority signs such that no vehicle enters the junction +- Fixed: When adding a junction to a timed traffic light, sometimes light states given by user input are not correctly stored +- Fixed: Joining two timed traffic lights sets the minimum time to "1" for steps with zero minimum time assigned +- Fixed: Modifications of timed traffic light states are sometimes not visible while editing the light (but they are applied nonetheless) +- Fixed: Button background is not always correctly changed after clicking on a button within the main menu + +### TM:PE 1.8.13, 05/01/2017 + +- Improved: Selection of overlay markers on underground roads (thanks to Padi for reminding me of that issue) +- Improved: Minor performance improvements +- Fixed: Timed traffic light data can become corrupt when upgrading a road segment next to a traffic light, leading to faulty UI behaviour (thanks to Brain for reporting this issue) +- Fixed: The position of the main menu button resets after switching to the free camera mode (thanks to Impact and gravage for reporting this issue) +- Fixed: A division by zero exception can occur when calculating the average number of waiting/floating vehicles + +### TM:PE 1.8.12, 02/01/2017 + +- Fixed: After leaving the "Manual traffic lights" mode the traffic light simulation is not cleaned up correctly (thanks to diezelunderwood for reporting this issue) +- Fixed: Insufficient access rights to log file causes the mod to crash +- Updated: Compatible with C:SL 1.6.2-f1 + +### TM:PE 1.8.11, 02/01/2017 + +- Fixed: Speed limits for elevated/underground road segments are sometimes not correctly loaded (thanks to Pirazel and [P.A.N] Uf0 for reporting this issue) + +### TM:PE 1.8.10, 31/12/2016 + +- Improved: Path-finding performance (a bit) +- Fixed: Check for invalid road thumbnails in the "custom default speed limits" dialog + +### TM:PE 1.8.9, 29/12/2016 + +- Added: It is now possible to set speed limits for metro tracks +- Added: Custom default speed limits may now be defined for train and metro tracks +- Improved: Customizable junctions are now highlighted by the lane connector tool +- Improved: UI behaviour +- Improved: Performance improvements +- Fixed: Selecting a junction to set up priority signs sometimes does not work (thanks to Artemis *Seven* for reporting this issue) +- Fixed: Automatic pedestrian lights do not work as expected at junctions with incoming one-ways and on left-hand traffic maps +- Updated: Junction restrictions may now be controlled at bend road segments + +### TM:PE 1.8.8, 25/12/2016 + +- Fixed: Taxis are not being used +- Fixed: Prohibiting u-turns with the junction restriction tool does not work (thanks to Kisoe for reporting this issue) +- Fixed: Cars are sometimes floating across the map while trying to park (thanks to [Delta ²k5] for reporting this issue) + +### TM:PE 1.8.7, 24/12/2016 + +- Added: Italian translation (thanks to Admix for translating) +- Improved: Advanced AI: Improved lane selection +- Improved: Overall user interface performance +- Improved: Overlay behaviour +- Improved: Traffic measurement +- Improved: Auto pedestrian lights at timed traffic lights behave more intelligently now +- Fixed: Parking AI - Cims that try to reach their parked car are sometimes teleported to another location where they start to fly through the map in order to reach their car +- Fixed: Parking AI - Cims owning a parked car do not consider using other means of transportation +- Fixed: Parking AI - Residents are unable to leave the city through a highway outside connection +- Fixed: Trains/Trams are sometimes not detected at timed traffic lights +- Updated: Compatible with C:SL 1.6.2-f1 +- Updated: The position of the main menu button is now forced inside screen bounds on startup +- Updated: A timed traffic light step with zero minimum time assigned can now be skipped automatically +- Updated: Using the lane connector to create a u-turn now automatically enables the "u-turn allowed" junction restriction +- Updated: French translation (thanks to simon.royer007 for translating) + +### C:SL 1.6.2-f1, 21/12/2016 + +- Fixed: Various errors in game + +### TM:PE 1.8.6, 12/12/2016 + +- Added: Korean language (thanks to Toothless FLY [ROK]LSh.st for translating) +- Updated: Chinese language code (zh-cn -> zh) in order to make it compatible with the game (thanks to Lost丶青柠 for reporting this issue) + +### TM:PE 1.8.5, 11/12/2016 + +- Fixed: Average speed limits are not correctly calculated for road segments with bicycle lanes (thanks to Toothless FLY [ROK]LSh.st for reporting this issue) +- Removed: "Evacuation busses may only be used to reach a shelter" (CO fixed this issue) +- Updated: Compatible with C:SL 1.6.1-f2 + +### TM:PE 1.8.4, 11/12/2016 + +- Added: "Stay on lane" - Press Shift + S in the Lane Connector tool can cycle through lane directions. +- Fixed: Bicycles cannot change from bicycle lanes to pedestrian lanes +- Fixed: Travel probabilities set in the "Citizen Lifecycle Rebalance v2.1" mod are not obeyed (thanks to informmanuel, shaundoddmusic for reporting this issue) +- Fixed: Number of tourists seems to drop when activating the mod (statistics were not updated, thanks to hpp7117, wjrohn for reporting this issue) +- Fixed: When loading a second savegame a second main menu button is displayed (thanks to Cpt. Whitepaw for reporting this issue) +- Fixed: While path-finding is in progress vehicles do "bungee-jumping" on the current segment (thanks to mxolsenx, Howzitworld for reporting this issue) +- Fixed: Cims leaving the city search for parking spaces near the outside connection which is obviously not required +- Updated: U-turns are now only allowed to be performed from the innermost lane +- Updated: TMPE now detects if the number of spawned vehicles is reaching its limit (16384). If so, spawning of service/emergency vehicles is prioritized over spawning other vehicles. + +### C:SL 1.6.1-f2, 11/12/2016 + +- Added: Missing service enumerators to modding API +- Fixed: Relocating emergency shelter breaks evacuation route +- Fixed: Citizens using evacuation routes like a bus route +- Fixed: Lots of other bug fixes to game and Disasters DLC +- Updated: Chinese localisation added + +### TM:PE 1.8.3, 4/12/2016 + +- Improved: Tweaked u-turn behaviour +- Improved: Info views +- Fixed: Despite having the Parking AI activated, cims sometimes still spawn pocket cars. +- Fixed: When the Parking AI is active, bicycle lanes are not used (thanks to informmanuel for reporting this issue) + +### TM:PE 1.8.2, 3/12/2016 + +- Fixed: Taxis were not used (thanks to [Delta ²k5] for reporting) +- Fixed: Minor UI fix in Default speed limits dialog + +### TM:PE 1.8.1, 1/12/2016 + +- Fixed: Mod crashed when loading a second savegame +- Updated: translations: Polish, Chinese (simplified) + +### TM:PE 1.8.0, 29/11/2016 + +- Added: Default speed limits +- Added: Parking AI (replaces "Prohibit cims from spawning pocket cars") +- Added: Main menu button is now moveable +- Added: Mod option - Heavy vehicles prefer outer lanes on highways +- Added: Mod option - Realistic speeds +- Added: Mod option - Evacuation busses may ignore traffic rules (Natural Disasters DLC required) +- Added: Mod option - Evacuation busses may only be used to reach a shelter (Natural Disasters DLC required) +- Added: Traffic info view shows parking space demand if Parking AI is activated +- Added: Public transport info view shows transport demand if Parking AI is activated +- Added: Info texts for citizen and vehicle tool tips if Parking AI is activated +- Improved: AI - Improved lane selection, especially on busy roads +- Improved: AI - Improved mean lane speed measurement +- Updated: Compatible with C:SL 1.6.0-f4 +- Updated: Extracted internal configuration to XML configuration file +- Updated: Changed main menu button due to changes in the game's user interface +- Updated: Translations for German, Portuguese, Russian, Dutch, Chinese (traditional) +- Removed: Compatibility check for Traffic++ V2 due to excessive workload + +### C:SL 1.6.0-f4 (Natural Disasters), 29/11/2016 + +- Added: Disasters +- Added: Disaster Recovery Service (Van and Helicopter) +- Added: Police / Fire / Ambulance Helicopters +- Added: Pumping Service & Trucks +- Added: Emergency Shelter & Evacuation Bus +- Added: Additional policies (eg. Helicopter Priority) +- Added: Ability for roads to be destroyed by disasters + +### TM:PE 1.7.15, 26/10/2016 + +- Fixed: Timed traffic lights window disappears when clicking on it with the middle mouse button (thanks to Nexus and Mariobro14 for helping me identifying the cause of this bug) + +### TM:PE 1.7.14, 18/10/2016 + +- Updated: Compatible with C:SL 1.5.2-f3 + +### TM:PE 1.7.13, 15/09/2016 + +- Added: Button to reset stuck vehicles/cims (see mod settings menu) +- Fixed: Implemented a permanent fix to solve problems with stuck vehicles/cims caused by third party mods +- Fixed: AI: Lane merging was not working as expected +- Fixed: Pedestrian light states were sometimes not being stored correctly (thanks to Filip for pointing out this problem) +- Updated: AI - Improved lane selection algorithm + +### TM:PE 1.7.12, 09/09/2016 + +- Fixed: Timed traffic lights should now correctly detect trains and trams +- Fixed: GUI: Junction restriction icons sometimes disappear +- Updated: AI - Lane changes are reduced on congested road segments +- Updated: Chinese (simplified) translation + +### TM:PE 1.7.11, 01/09/2016 + +- Updated: Compatible with C:SL 1.5.1-f3 + +### TM:PE 1.7.10, 31/08/2016 + +- Added: Players can now disable spawning of pocket cars +- Fixed: Timed traffic lights were flickering +- Fixed: Pedestrian traffic lights were not working as expected +- Fixed: When upgrading/removing/adding a road segment, nearby junction restrictions were removed +- Fixed: Setting up vehicle restrictions affects trams (thanks to chem for reporting) +- Fixed: Manual pedestrian traffic light states were not correctly handled +- Fixed: Junction restrictions overlay did not show all restricted junctions +- Updated: Chinese (simplified) translation + +### TM:PE 1.7.9, 22/08/2016 + +- Improved: Performance improvements +- Fixed: In-game traffic light states are now correctly rendered when showing "yellow" +- Fixed: GUI - Traffic light states do not flicker anymore +- Removed: Negative effects on public transport usage + +### TM:PE 1.7.8, 18/08/2016: + +- Fixed: Cims sometimes got stuck (thanks to all reports and especially to Thilawyn for providing a savegame) +- Improved: GUI - Better traffic light arrow display +- Improved: Performance while saving + +### TM:PE 1.7.7, 16/08/2016: + +- Added: "110" speed limit +- Improved: Performance while saving +- Improved: GUI - Windows are draggable +- Improved: GUI - Improved window scaling on lower resolutions +- Updated: AI - Instead of walking long distances, citizens now use a car +- Updated: AI - Citizens will remember their last used mode of transport (e.g. they will not drive to work and come return by bus anymore) +- Updated: AI - Increased path-finding costs for traversing over restricted road segments + +### TM:PE 1.7.6, 14/08/2016: + +- Added: Players may now prohibit cims from crossing the street +- Added: the possibility to connect train track lanes with the lane connector (as requested by pilot.patrick93) +- Improved: UI - Clicking with the secondary mouse button now deselects the currently selected node/segment for all tools +- Improved: AI - Tuned randomization of lane changing behaviour +- Fixed: AI: At specific junctions, vehicles were not obeying lane connections correctly (thanks to Mariobro14 for pointing out this problem) +- Fixed: AI: Path-finding costs for u-turns were not correctly calculated (thanks to Mariobro14 for pointing out this problem) +- Fixed: Vehicles were endlessly waiting for each other at junctions with certain priority sign configurations +- Fixed: AI: Lane changing costs corrected +- Updated: AI - Introduced path-finding costs for leaving main highway (should reduce amount of detours taken) +- Updated: Moved options from "Change lane arrows" to "Vehicle restrictions" tool +- Updated: Russian translation + +### TM:PE 1.7.5, 07/08/2016: + +- Fixed: AI - Cims were using pocket cars whenever possible +- Fixed: AI - Path-finding failures led to much less vehicles spawning +- Fixed: AI - Lane selection at junctions with custom lane connection was not always working properly (e.g. for Network Extensions roads with middle lane) +- Fixed: While editing a timed traffic light it could happen that the traffic light was deleted + +### TM:PE 1.7.4, 31/07/2016: + +- Added: French translations (thanks to simon.royer007 for translating!) +- Improved: AI - Tuned new parameters +- Improved: Various code improvements +- Fixed: Activated/Disabled features were not loaded correctly +- Fixed: AI - At specific junctions the lane changer did not work as intended +- Fixed: Possible fix for OSX performance issues +- Updated: AI - Switched from relative to absolute traffic density measurement + +### TM:PE 1.7.3, 29/07/2016: + +- Added: Ability to enable/disable mod features (e.g. for performance reasons) +- Improved: Further code improvements +- Fixed: Vehicle type determination was incorrect (fixed u-turning trams/trains, stuck vehicles) +- Fixed: Clicking on a train/tram node with the lane connector tool led to an uncorrectable error (thanks to noaccount for reporting this problem) + +### TM:PE 1.7.2, 26/07/2016: + +- Improved: Optimized UI overlay performance + +### TM:PE 1.7.1, 24/07/2016: +- Fixed: Trains were not despawning if no path could be calculated +- Fixed: Workaround for third-party issue: TM:PE now detects if the calculation of total vehicle length fails +- Removed: "Busses now may only ignore lane arrows if driving on a bus lane" + +### TM:PE 1.7.0, 23/07/2016: + +- Added: Traffic++ lane connector +- Added: Compatibility detection for the Rainfall mod +- Fixed: Busses now may only ignore lane arrows if driving on a bus lane +- Improved: performance of priority sign rules +- Improved: Better UI performance if overlays are deactivated +- Improved: Better fault-tolerance of the load/save system +- Fixed: Taxis were allowed to ignore lane arrows +- Fixed: AI - Highway rules on left-hand traffic maps did not work the same as on right-hand traffic maps +- Fixed: Upgrading a road segment next to a timed traffic light removed the traffic light leading to an inconsistent state (thanks to ad.vissers for pointing out this problem) +- Updated: AI - Cims now ignore junctions where pedestrian lights never change to green +- Updated: AI - Removed the need to define a lane changing probability +- Updated: AI - Tweaked lane changing parameters +- Updated: AI - Highway rules are automatically disabled at complex junctions (= more than 1 incoming and more than 1 outgoing roads) +- Updated: Simulation accuracy now also controls time intervals between traffic measurements +- Updated: Default wait-flow balance is set to 0.8 +- Updated: Rewritten and simplified vehicle position tracking near timed traffic lights and priority signs for performance reasons + +### TM:PE 1.6.22, 29/06/2016: + +- Fixed: Traffic measurement at timed traffic lights was incorrect +- Updated: AI - Taxis now may not ignore lane arrows and are using bus lanes whenever possible (thanks to Cochy for pointing out this issue) +- Updated: AI - Busses may only ignore lane arrows while driving on a bus lane + +### TM:PE 1.6.22, 21/06/2016: + +- Improved: Advanced Vehicle AI - Improved lane selection at junctions where bus lanes end +- Improved: Advanced Vehicle AI - Improved lane selection of busses +- Improved: Speed/vehicle restrictions may now be applied to all road segments between two junctions by holding the shift key +- Improved: Automatic pedestrian lights +- Improved: Separate traffic lights: Traffic lights now control traffic lane-wise +- Fixed: Lane selection on maps with left-hand traffic was incorrect +- Fixed: While building in pause mode, changes in the road network were not always recognized causing vehicles to stop/despawn +- Fixed: Police cars off-duty were ignoring lane arrows +- Fixed: If public transport stops were near a junction, trams/busses were not counted by timed traffic lights (many thanks to Filip for identifying this problem) +- Fixed: Trains/Trams were sometimes ignoring timed traffic lights (many thanks to Filip for identifying this problem) +- Fixed: Building roads with bus lanes caused garbage, bodies, etc. to pile up +- Updated: Reworked how changes in the road network are recognized +- Updated: Sensitivity slider is only available while adding/editing a step or while in test mode + +### TM:PE 1.6.21, 14/06/2016: + +- Fixed: Too few cargo trains were spawning (thanks to Scratch, toruk_makto1, Mr.Miyagi, mottoh and Syparo for pointing out this problem) +- Fixed: Vehicle restrictions did not work as expected (thanks to nordlaser for pointing out this problem) + +### TM:PE 1.6.20, 11/06/2016: + +- Fixed: Priority signs were not working correctly (thanks to mottoth, Madgemade for pointing out this problem) + +### TM:PE 1.6.19, 11/06/2016 + +- Fixed: Timed traffic lights UI not working as expected (thanks to Madgemade for pointing out this problem) + +### TM:PE 1.6.18, 09/06/2016 + +- Improved: Players can now select elevated rail segments/nodes +- Improved: Trams and trains now follow priority signs +- Improved: performance of priority signs and timed traffic lights +- Improved: UI behaviour when setting up priority signs +- Updated: Compatible with C:SL 1.5.0-f4 + +### C:SL 1.5.0-f4 (Match Day), 09/06/2016 + +- Added: Football stadium (causes heavy traffic in city on match day) +- Fixed: Various bugs in game and DLCs + +### TM:PE 1.6.17, 20/04/2016 + +- Fixed: Hotfix for reported path-finding problems + +### TM:PE 1.6.16, 19/04/2016 + +- Updated: Compatible with C:SL 1.4.1-f2 + +### C:SL 1.4.1-f2, 19/04/2016 + +- Fixed: Busses could enter/exit bus stations at highways with sound barriers +- Fixed: Various other bugs + +### TM:PE 1.6.15, 22/03/2016 + +- Added: Traditional Chinese translation +- Improved: Possible fix for crashes described by cosminel1982 +- Updated: Compatible with C:SL 1.4.0-f3 + +### C:SL 1.4.0-f3, 22/03/2016 + +- Fixed: Lots of bugs in game and DLCs +- Updated: Lots of stuff in game and DLCs + +### TM:PE 1.6.14, 17/03/2016 + +- Fixed: Cargo trucks did not obey vehicle restrictions (thanks to ad.vissers for pointing out this problem) +- Fixed: When Advanced AI was deactivated, u-turns did not have costs assigned + +### TM:PE 1.6.13, 16/03/2016 + +- Added: Dutch translation +- Improved: The pedestrian light mode of a traffic light can now be switched back to automatic +- Improved: Vehicles approaching a different speed limit change their speed more gradually +- Improved: Path-finding performance improvements +- Improved: Fine-tuned path-finding lane changing behaviour +- Fixed: After loading another savegame, timed traffic lights stopped working for a certain time +- Fixed: Lane speed calculation corrected +- Updated: The size of signs and symbols in the overlay is determined by screen resolution height, not by width + +### TM:PE 1.6.12, 03/03/2016 + +- Improved: Reduced memory usage +- Fixed: Adding/removing junctions to/from existing timed traffic lights did not work (thanks to nieksen for pointing out this problem) +- Fixed: Separate timed traffic lights were sometimes not saved (thanks to nieksen for pointing out this problem) +- Fixed: Fixed an initialization error (thanks to GordonDry for pointing out this problem) + +### TM:PE 1.6.11, 03/03/2016 + +- Added: Chinese translation +- Added: By pressing "Page up"/"Page down" you can now switch between traffic and default map view +- Improved: Size of information icons and signs is now based on your screen resolution +- Updated: UI code refactored + +### TM:PE 1.6.10, 02/03/2016 + +- Added: Additional controls for vehicle restrictions added +- Fixed: Clicking on a Traffic Manager overlay resulted in vanilla game components (e.g. houses, vehicles) being activated + +### TM:PE 1.6.9, 02/03/2016 + +- Updated: Compatibility with C:SL 1.3.2-f1 + +### C:SL 1.3.2-f1, 02/03/2016 + +- Fixed: Stuck pedestrian issues +- Fixed: Short roads warning +- Fixed: Cyclists despawn when changing from bike path to bike lane + +### TM:PE 1.6.8, 01/03/2016 + +- Added: Spanish translation +- Improved: Major path-finding performance improvements +- Updated: Japanese translation (thanks to Akira Ishizaki for translating!) + +### TM:PE 1.6.7, 27/02/2016 + +- Improved: Tuned AI parameters +- Improved: Traffic density measurements +- Improved: Lane changing near junctions - reintroduced costs for lane changing before junctions +- Improved: Vehicle behaviour near blocked roads (e.g. while a building is burning) +- Fixed: Automatic pedestrian lights for outgoing one-ways fixed +- Fixed: U-turns did not have appropriate costs assigned +- Fixed: The time span between AI traffic measurements was too high + +### TM:PE 1.6.6, 27/02/2016 + +- Improved: Easier to select segment ends in order to change lane arrows. +- Fixed: U-turning vehicles were not obeying the correct directional traffic light (thanks to t1a2l for pointing out this problem) +- Updated: Priority signs now cannot be setup at outgoing one-ways. +- Updated: French translation (thanks to simon.royer007 for translating!) +- Updated: Polish translation (thanks to Krzychu1245 for translating!) +- Updated: Portuguese translation (thanks to igordeeoliveira for translating!) +- Updated: Russian translation (thanks to FireGames for translating!) + +### TM:PE 1.6.5, 24/02/2016 + +- Added: Despawning setting to options dialog +- Improved: Detection of Traffic++ V2 + +### TM:PE 1.6.4, 23/02/2016 + +- Improved: Minor performance improvements +- Fixed: Path-finding calculated erroneous traffic density values +- Fixed: Cims left the bus just to hop on a bus of the same line again (thanks to kamzik911 for pointing out this problem) +- Fixed: Despawn control did not work (thanks to xXHistoricalxDemoXx for pointing out this problem) +- Fixed: State of new settings was not displayed correctly (thanks to Lord_Assaultーさま for pointing out this problem) +- Fixed: Default settings for vehicle restrictions on bus lanes corrected +- Fixed: Pedestrian lights at railway junctions fixed (they are still invisible but are derived from the car traffic light state automatically) + +### C:SL 1.3.1-f1, 23/02/2016 + +- Fixed: Minor bug fixes and updates + +### TM:PE 1.6.3, 22/02/2016 + +- Fixed: Using the "Old Town" policy led to vehicles not spawning. +- Fixed: Planes, cargo trains and ship were sometimes not arriving +- Fixed: Trams are not doing u-turns anymore + +### TM:PE 1.6.2, 20/02/2016 + +- Added: Trams are now obeying speed limits (thanks to Clausewitz for pointing out the issue) +- Fixed: Clear traffic sometimes throwed an error +- Fixed: Vehicle restrictions did not work as expected (thanks to [Delta ²k5] for pointing out this problem) +- Fixed: Transition of automatic pedestrian lights fixed + +### TM:PE 1.6.1, 20/02/2016 + +- Improved: Performance +- Improved: Modifying mod options through the main menu now gives an annoying warning message instead of a blank page. +- Fixed: Various UI issues + +### TM:PE 1.6.0, 18/02/2016 + +- Added: Separate traffic lights for different vehicle types +- Added: Vehicle restrictions +- Added: "Vehicles may enter blocked junctions" may now be defined for each junction separately (again) +- Added: Road conditions (snow, maintenance state) may now have a higher impact on vehicle speed (see "Options" menu) +- Improved: Better handling of vehicle bans +- Improved: Method for calculating lane traffic densities +- Improved: Performance optimizations +- Improved: Advanced Vehicle AI: Improved lane spreading +- Fixed: Reckless drivers now do not enter railroad crossings if the barrier is down +- Fixed: Path-finding costs for crossing a junction fixed +- Fixed: Vehicle detection at timed traffic lights did not work as expected +- Fixed: Not all valid traffic light arrow modes were reachable +- Updated: Compatible with C:SL 1.3.0-f4 +- Updated: Ambulances, fire trucks and police cars on duty are now ignoring lane arrows +- Updated: Timed traffic lights may now be setup at arbitrary nodes on railway tracks +- Updated: Option dialog is disabled if accessed through the main menu +- Updated: Vehicles going straight may now change lanes at junctions +- Updated: Vehicles may now perform u-turns at junctions that have an appropriate lane arrow configuration +- Updated: Emergency vehicles on duty now always aim for the fastest route + +### C:SL 1.3.0-f4 (Snowfall), 18/02/2016 + +- Added: Winter biome +- Added: Trams +- Added: Snow ploughs +- Added: Road maintenance +- Added: Road conditions affect vehicle speed +- Added: Priority routes + +### TM:PE 1.5.2, 01/02/2016 + +- Added: Traffic lights may now be added to/removed from underground junctions +- Added: Traffic lights may now be setup at some points of railway tracks (there seems to be a game-internal bug that prevents selecting arbitrary railway nodes) +- Added: Display of priority signs, speed limits and timed traffic lights may now be toggled via the options dialog +- Fixed: Reckless driving does not apply for trains (thanks to GordonDry for pointing out this problem) +- Fixed: Manual traffic lights were not working (thanks to Mas71 for pointing out this problem) +- Fixed: Pedestrians were ignoring timed traffic lights (thanks to Hannes8910 for pointing out this problem) +- Fixed: Sometimes speed limits were not saved (thanks to cca_mikeman for pointing out this problem) + +### TM:PE 1.5.1, 31/01/2016 + +- Added: Trains are now following speed limits + +### TM:PE 1.5.0, 30/01/2016 + +- Added: Speed restrictions (as requested by Gfurst) +- Improved: AI - Parameters tuned +- Improved: Code improvements +- Fixed: Flowing/Waiting vehicles count corrected +- Updated: Lane arrow changer window is now positioned near the edited junction (as requested by GordonDry) + +### TM:PE 1.4.9, 27/01/2016 + +- Added: Junctions can be added to/removed from timed traffic lights after they are created +- Improved: When viewing/moving a timed step, the displayed/moved step is now highlighted (thanks to Joe for this idea) +- Improved: Performance improvements +- Fixed: AI - Fixed a division by zero error (thanks to GordonDry for pointing out this problem) +- Fixed: AI - Near highway exits vehicles tended to use the outermost lane (thanks to Zake for pointing out this problem) +- Fixed: Some lane arrows disappeared on maps using left-hand traffic systems (thanks to Mas71 for pointing out this problem) +- Fixed: In lane arrow edit mode, the order of arrows was sometimes incorrect (thanks to Glowstrontium for pointing out this problem) +- Fixed: Lane merging in left-hand traffic systems fixed +- Fixed: Turning priority roads fixed (thanks to GordonDry for pointing out this problem) + +### TM:PE 1.4.8, 25/01/2016 + +- Added: translation to Polish (thanks to Krzychu1245 for working on this!) +- Added: translation to Russian (thanks to FireGames for working on this!) +- Improved: AI - Parameters have been tuned +- Improved: AI - Added traffic density measurements +- Improved: Performance improvements +- Fixed: After removing a timed or manual light the traffic light was deleted (thanks to Mas71 for pointing out this problem) +- Fixed: Segment geometries were not always calculated +- Fixed: In highway rule mode, lane arrows sometimes flickered +- Fixed: Some traffic light arrows were sometimes not selectable + +### TM:PE 1.4.7, 22/01/2016 + +- Added: Translation to Portuguese (thanks to igordeeoliveira for working on this!) +- Improved: Reduced file size (thanks to GordonDry for reporting this problem) +- Fixed: Freight ships/trains were not coming in (thanks to Mas71 and clus for reporting this problem) +- Fixed: The toggle "Vehicles may enter blocked junctions" did not work properly (thanks for exxonic for reporting this problem) +- Fixed: If a timed traffic light is being edited the segment geometry information is not updated (thanks to GordonDry for reporting this problem) + +### TM:PE 1.4.6, 22/01/2016 + +- Added: Running average lane speeds are measured now +- Fixed: Minor bug fixes + +### TM:PE 1.4.5, 22/01/2016 + +- Added: "Vehicles may enter blocked junctions" may now be defined for each junction separately +- Fixed: A deadlock in the path-finding is fixed +- Fixed: Small timed light sensitivity values (< 0.1) were not saved correctly +- Fixed: Timed traffic lights were not working for some players +- Updated: Refactored segment geometry calculation + +### TM:PE 1.4.4, 21/01/2016 + +- Added: Localization support + +### TM:PE 1.4.3, 20/01/2016 + +- Improved: Several performance improvements +- Improved: Calculation of segment geometries +- Improved: Load balancing +- Improved: Police cars, ambulances, fire trucks and hearses are now also controlled by the AI +- Fixed: Vehicles did not always take the shortest path +- Fixed: Vehicles disappeared after deleting/upgrading a road segment +- Fixed: Fixed an error in path-finding cost calculation +- Fixed: Outgoing roads were treated as ingoing roads when highway rules were activated + +### TM:PE 1.4.2, 16/01/2016 + +- Added: Option added to disable highway rules +- Improved: Several major performance improvements (thanks to sci302 for pointing out those issues) +- Improved: Saving/loading of timed traffic lights +- Fixed: AI did not consider speed limits/road types during path calculation (thanks to bhanhart, sa62039 for pointing out this problem) +- Fixed: Vehicles were stopping in front of green traffic lights +- Fixed: Stop/Yield signs were not working properly (thanks to GordonDry, Glowstrontium for pointing out this problem) +- Fixed: Cargo trucks were ignoring the "Heavy ban" policy, they should do now (thanks to Scratch for pointing out this problem) +- Updated: Lane-wise traffic density is only measured if Advanced AI is activated +- Meta: Connecting a city road to a highway road that does not supply enough lanes for merging leads to behaviour people do not understand (see manual). + +### TM:PE 1.4.1, 15/01/2016 + +- Fixed: Path-finding near junctions fixed + +### TM:PE 1.4.0, 15/01/2016 + +- Added: Advanced Vehicle AI (disabled by default! Go to "Options" and enable it if you want to use it.) +- Fixed: Traffic lights were popping up in the middle of roads +- Fixed: Fixed the lane changer for left-hand traffic systems (thanks to Phishie for pointing out this problem) +- Fixed: Traffic lights on invalid nodes are not saved anymore + +### TM:PE 1.3.24, 13/01/2016 + +- Added: Configuration option that allows vehicles to enter blocked junctions +- Improved: Priority signs: After adding two main road signs the next offered sign is a yield sign +- Improved: Priority signs: Vehicles now should notice earlier that they can enter a junction +- Fixed: Invalid (not created) lanes are not saved/loaded anymore +- Fixed: Some priority signs were not saved +- Fixed: Priority signs on deleted segments are now deleted too +- Fixed: Lane arrows on removed lanes are now removed too +- Fixed: Adding a priority sign to a junction having more than one main sign creates a yield sign (thanks to GordonDry for pointing out this problem) +- Fixed: If reckless driving was set to "The Holy City (0 %)", vehicles blocked intersections with traffic light. +- Fixed: Traffic light arrow modes were sometimes not correctly saved +- Removed: Legacy XML file save system + +### TM:PE 1.3.23, 09/01/2016 + +- Added: Option added to forget all toggled traffic lights +- Fixed: Corrected an issue where toggled traffic lights would not be saved/loaded correctly (thanks to Jeffrios and AOD_War_2g for pointing out this problem) + +### TM:PE 1.3.22, 08/01/2016 + +- Added: Option allowing busses to ignore lane arrows +- Added: Option to display nodes and segments + +### TM:PE 1.3.21, 06/01/2016 + +- Added: Traffic Sensitivity Tuning +- Improved: When adding a new step to a timed traffic light the lights are inverted. +- Improved: Timed traffic light status symbols should now be less annoying +- Fixed: Deletion of junctions that were members of a traffic light group is now handled correctly + +### TM:PE 1.3.20, 04/01/2016 + +- Added: Reckless driving +- Improved: User interface +- Fixed: Timed traffic lights are not saved correctly after upgrading a road nearby + +### TM:PE 1.3.19, 04/01/2016 +- Improved: Timed traffic lights: Velocity of vehicles is being measured to detect traffic jams +- Improved: Traffic flow measurement +- Improved: Path finding - Cims may now choose their lanes more independently +- Fixed: Upgrading a road resets the traffic light arrow mode +- Updated: Timed traffic lights: Absolute minimum time changed to 1 + +### TM:PE 1.3.18, 03/01/2016 + +- Improved: You can now switch between activated timed traffic lights without clicking on the menu button again +- Fixed: Provided a fix for unconnected junctions caused by other mods +- Removed: Crosswalk feature removed. If you need to add/remove crosswalks please use the "Crossings" mod. + +### TM:PE 1.3.17, 03/01/2016 + +- Fixed: Timed traffic lights cannot be added again after removal, toggling traffic lights does not work (thanks to Fabrice, ChakyHH, sensual.heathen for pointing out this problem) +- Fixed: After using the "Manual traffic lights" option, toggling lights does not work (thanks to Timso113 for pointing out this problem) + +### TM:PE 1.3.16, 03/01/2016 + +- Fixed: Traffic light settings on roads of the Network Extensions mods are not saved (thanks to Scarface, martintech and Sonic for pointing out this problem) +- Improved: Save data management + +### TM:PE 1.3.15, 02/01/2016 + +- Added: Simulation accuracy (and thus performance) is now controllable through the game options dialog +- Fixed: Vehicles on a priority road sometimes stop without an obvious reason + +### TM:PE 1.3.14, 01/01/2016 + +- Improved: Performance +- Improved: Non-timed traffic lights are now automatically removed when adding priority signs to a junction +- Improved: Adjusted the adaptive traffic light decision formula (vehicle lengths are considered now) +- Improved: Traffic two road segments in front of a timed traffic light is being measured now + +### TM:PE 1.3.13, 01/01/2016 + +- Fixed: Lane arrows are not correctly translated into path finding decisions (thanks to bvoice360 for pointing out this problem) +- Fixed: Priority signs are sometimes undeletable (thank to Blackwolf for pointing out this problem) +- Fixed: Errors occur when other mods without namespace definitions are loaded (thanks to Arch Angel for pointing out this problem) +- Fixed: Connecting a new road segment to a junction that already has priority signs now allows modification of the new priority sign + +### TM:PE 1.3.12, 30/12/2015 + +- Fixed: Priority signs are not editable (thanks to ningcaohan for pointing out this problem) + +### TM:PE 1.3.11, 30/12/2015 + +- Improved: Road segments next to a timed traffic light may now be deleted/upgraded/added without leading to deletion of the light +- Updated: Priority signs and Timed traffic light state symbols are now visible as soon as the menu is opened + +### TM:PE 1.3.10, 29/12/2015 +- Fixed: an issue where timed traffic light groups were not deleted after deleting an adjacent segment + +### TM:PE 1.3.9, 29/12/2015 + +- Added: Introduced information icons for timed traffic lights +- Updated: Mod is now compatible with "Improved AI" (Lane changer is deactivated if "Improved AI" is active) + +### TM:PE 1.3.8, 29/12/2015 + +- Improved: UI improvements +- Fixed: Articulated busses are now simulated correctly (thanks to nieksen for pointing out this problem) + +### TM:PE 1.3.7, 28/12/2015 + +- Fixed: When setting up a new timed traffic light, yellow lights from the real-world state are not taken over +- Fixed: When loading another save game via the escape menu, Traffic Manager does not crash +- Fixed: When loading another save game via the escape menu, Traffic++ detection works as intended +- Fixed: Lane arrows are saved correctly + +### TM:PE 1.3.6, 28/12/2015 + +- Fixed: wrong flow value taken when comparing flowing vehicles +- Updated: Forced node rendering after modifying a crosswalk + +### TM:PE 1.3.5, 28/12/2015 + +- Improved: Adjusted the comparison between flowing (green light) and waiting (red light) traffic +- Fixed: Pedestrian traffic Lights (thanks to Glowstrontium for pointing out this problem) +- Fixed: Deleting a segment with a timed traffic light does not cause a NullReferenceException + +### TM:PE 1.3.4, 27/12/2015 + +- Improved: Better traffic jam handling + +### TM:PE 1.3.3, 27/12/2015 + +- Improved: Hotfix - Deleting a segment with a timed traffic light does not cause a NullReferenceException +- Updated: If priority signs are located behind the camera they are not rendered anymore + +### TM:PE 1.3.2, 27/12/2015 + +- Fixed: Synchronized traffic light rendering: In-game Traffic lights display the correct color (Thanks to Fabrice for pointing out this problem) +- Fixed: Traffic lights switch between green, yellow and red. Not only between green and red. +- Updated: Priority signs are persistently visible when Traffic Manager is in "Add priority sign" mode +- Updated: UI tool tips are more explanatory and are shown longer. + +### TM:PE 1.3.1, 26/12/2015 + +- Fixed: Timed traffic lights of deleted/modified junctions get properly disposed +- Updated: Minimum time units may be zero now (timed traffic lights) + +### TM:PE 1.3.0, 25/12/2015 + +- Added: Adaptive Timed Traffic Lights (automatically adjusted based on traffic amount) +- Meta: First release in Steam Workshop under name "Traffic Manager: President Edition" +- Meta: Fork of TMPlus 1.2.0 created by LinuxFan (github user: VictorPhilipp) + +### TMPlus 1.2.0, 04/12/2015 + +- Updated: Game version 1.2.2-f2 compatible + +### C:SL 1.2.2-f2, 05/11/2015 + +- Updated: Limits for zoning, buildings, road segments increased +- Fixed: Various bugfixes in game and asset editor + +### TM 1.0.9rc, 12/10/2015 + +- Removed: Tool - Pedestrian crosswalks (too buggy) +- Meta: Released as version 1.09rc +- Meta: Last release of original Traffic Manager mod by CBeTHaX (aka SvetlozarValchev) + +### TM 1.0.9rc, 12/10/2015 + +- Fixed: Minor bugs +- Meta: Released as version 1.09rc + +### TM 1.0.9rc, 12/10/2015 + +- Update: Compatible with C:SL 1.2.1-f1 +- Meta: Released as version 1.09rc + +### C:SL 1.2.1-f1, 01/10/2015 + +- Fixed: Minor bugfixes with game and asset editor + +### C:SL 1.2 (After Dark), 24/09/2015 + +- Added: Bicycles +- Added: Bus and bike lanes +- Added: Prison Vans +- Added: Taxis + +### TMPlus 1.1.1, 09/08/2015 + +- Fixed: Load/save system +- Fixed: Saving no longer opens dev console + +### TM 1.0.6, 07/08/2015 + +- Update: Revert to old version with C:S 1.1.1 compatibility +- Meta: Released as version 1.06 + +### TMPlus 1.1.1, 06/08/2015 + +- Fixed: Null reference error when loading old savegames +- Updated: New save system +- Updated: New load system +- Meta: Lots of code refactoring + +### TM 1.0.5, 28/07/2015 + +- Fixed: Pathfinder bugs +- Fixed: Lane changer and traffic remover features sometimes disabled even without Traffic++ +- Fixed: Turn direction not matching lane marking logic (thanks klparrot!) +- Updated: Code refactor and clean-up +- Updated: Compatible with C:SL 1.1.1 +- Meta: Released as version 1.05 + +### TMPlus 1.1.1, 26/07/2015 + +- Fixed: Stop signs not working + +### TMPlus 1.1.1, 15/07/2015 + +- Fixed: PathManager. Should now work for people without Traffic++ +- Meta: Last confirmed Workshop release of "Traffic Manager Plus" + +### TMPlus 1.1.1, 15/07/2015 + +- Updated: Reverted PathFinder as it's causing Null Reference exceptions. Need to refactor it. + +### TMPlus 1.1.1, 14/07/2015 + +- Added: Support for tunnels +- Fixed: Turn direction not matching lane marking logic (thanks klparrot!) +- Fixed: Compilation errors +- Improved: Custom AI code refactoring and clean-up +- Improved: Tool code refactoring and clean-up +- Improved: UI code refactoring and clean-up +- Updated: Compatible with C:SL 1.1.1 +- Meta: Project fork by sieggy +- Meta: First release in Steam Workshop under name "Traffic Manager Plus" + +### C:SL 1.1.1, 01/07/2015 + +- Added: Tunnels for pedestrian paths +- Added: Autosave feature +- Fixed: Lots of stuff +- Improved: Asset editor + +### TM 1.0.4, 29/05/2015 + +- Fixed: Negative timers (thanks XaBBoK!) +- Improved: Pathfinder cleanup (thanks dornathal!) + +### TM 1.0.4 hotfix, 19/05/2015 + +- Fixed: Support for tunnels in AIs and pathfinder +- Update: Compatible with C:SL 1.1 +- Update: Traffic++ Compatibility - No despawn or lane changer available in compatibility mode + +### C:SL 1.1, 19/05/2015 + +- Added: European theme +- Added: Tunnels for roads and rail +- Improved: Metro tunnels can be built at different heights + +### TM 1.0.4 beta, 22/04/2015 + +- Added: Buttons for timed traffic light states: skip and move up/down +- Fixed: Null reference exceptions when placing a road +- Fixed: Pathfinder issues +- Improved: Lane changes +- Updated: Prevent lane changer being used on non-node segments +- Meta: Released as version 1.04rc + +### TM 1.0.1, 21/04/2015 + +- Fixed: Errors on very close segments added to timed traffic lights +- Fixed: Save/load traffic lights going negative (finally) +- Meta: Released as version 1.01rc + +### TM 1.0.0, 21/04/2015 + +- Fixed: Wrong lane change (finally) +- Fixed: Timed traffic lights should no longer go below zero +- Fixed: UI problems and positioning +- Meta: First release in Steam Workshop under name "Traffic Manager" +- Meta: Released as version 1.0rc + +### TM 0.9.0, 21/04/2015 + +- Fixed: Cars and service stop working +- Fixed: Wrong lane use +- Updated: Manager no longer loads in map editor +- Fixed: Errors on adding additional segment add +- Fixed: Negative numbers for traffic lights +- Added: Thumbnail to workshop page +- Meta: New save id +- Meta: Released as version 0.9b + +### TM 0.8.5, 19/04/2015 + +- Fixed lane merges in Left Hand Traffic maps +- Meta: Versions 0.8.3 and 0.8.4 were skipped + +### TM 0.8.2, 19/04/2015 + +- Updated: Rename 'Manual Control' to 'Manual Traffic Lights' + +### TM 0.8.1, 19/04/2015 + +- Added: Tool - Toggle Traffic Lights +- Updated: No more deleting trains on 'clear traffic' +- Fixed: Null exception timed traffic lights on node upgrade +- Fixed: UI position on different resolutions +- Meta: Released as version 0.8b + +### TM 0.8.0, 19/04/2015 + +- Fixed: Can now load game from pause menu + +### TM 0.7.1, 18/04/2015 + +- Improved: Tuned car wait on non-priority road + +### TM 0.7.0, 18/04/2015 + +- Fixed: Lanes on save/load in Left Hand traffic maps +- Fixed: Lane positions +- Fixed: More save/load fixes +- Fixed: Various timed lights problems +- Fixed: Traffic Manager load on multiple game loads +- Fixed: Multiple saves on one savegame +- Updated: Moved UI button + +### TM 0.6.1, 18/04/2015 + +- Fixed: Cars coming form wrong lane in Left Hand Traffic maps +- Fixed: Not being able to save on new game +- Fixed: Errors when pressing Esc before opening menu +- Meta: Released as version 0.61b + +### TM 0.6.0, 18/04/2015 + +- Added: Tool - Manual Control (traffic lights) +- Added: Tool - Priority Signs +- Added: Tool - Toggle Pedestrian Crossings +- Added: Tool - Clear Traffic +- Added: Tool - Vehicle Restrictions (Transport, Service, Cargo or Car) +- Added: Option - No Despawn (thanks cope) +- Added: Custom Pathfinder +- Added: Settings stored in save game +- Updated: Compatible with C:SL 1.0.7c +- Updated: Custom traffic light steps can be timed +- Updated: Custom Road AI +- Updated: Better traffic light UI +- Meta: Using cities-skylines-detour by cope (sschoener on github) + +### C:SL 1.0.7c, 07/04/2015 + +- Fixed: Lots of stuff + +### C:SL 1.0.7, 27/03/2015 + +- Fixed: Lots of stuff + +### TM 0.5.0, 22/03/2015 + +- Added: Tool - Traffic Lights Editor +- Added: Tool - Lane Changer +- Added: Toolbar for mod features +- Added: Log file +- Updated: Compatible with C:SL 1.0.6 + +### C:SL 1.0.6, 19/03/2015 + +- Fixed: Lots of stuff + +### TM 0.4.0, 14/03/2015 + +- Meta: Traffic Manager project officially started (at v0.4 for some reason!?) +- Meta: Github repository created by CBeTHaX (github user SvetlozarValchev) + +### C:SL 1.0, 10/03/2015 + +- New: Cities: Skylines 1.0 official release From 1a3fa302a644e81dbbf6ee823e3c766805e52ef6 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Sat, 6 Jul 2019 21:05:38 +0100 Subject: [PATCH 135/142] Only perform logging in DEBUG builds Fixes #411 --- TLM/TLM/UI/TextureResources.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index c5108b9a6..19ac7eb5c 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -272,9 +272,11 @@ public static Texture2D GetSpeedLimitTexture(float speedLimit, MphSignStyle mphS // Trim the index since 140 km/h / 90 MPH is the max sign we have var upper = mph ? SpeedLimit.UPPER_MPH : SpeedLimit.UPPER_KMPH; - if (index > upper) { +#if DEBUG + if (index > upper) { Log.Info($"Trimming speed={speedLimit} index={index} to {upper}"); } +#endif var trimIndex = Math.Min(upper, Math.Max((ushort)0, index)); return textures[trimIndex]; } From de201aa6c052bf4c507df97fa7ac115cf302b31d Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Sat, 6 Jul 2019 21:20:28 +0100 Subject: [PATCH 136/142] Version 10.21.1 hotfix Just changed version number --- TLM/TLM/TrafficManagerMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 40fe9d8f2..e374aceda 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -14,7 +14,7 @@ public class TrafficManagerMod : IUserMod { public static readonly uint GameVersionBuild = 2u; // Note: `Version` is also used in UI/MainMenu/VersionLabel.cs - public static readonly string Version = "10.21"; + public static readonly string Version = "10.21.1"; #if LABS public string Branch => "LABS"; From 0aa0c801a78ce09cbdb303c8cf63fcf968de0a23 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Sat, 6 Jul 2019 22:09:31 +0100 Subject: [PATCH 137/142] Updated changelogs for 10.21.1 --- CHANGELOG.md | 6 ++++++ README.md | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68017887d..68f09b997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Cities: Skylines - Traffic Manager: *President Edition* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) # Changelog + +#### TM:PE [10.21.1 hotfix](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.21...10.21.1), 06/07/2019 + +- Fixed: Speed panel tanks fps if train tracks on screen (thanks rlas & DaEgi01!) (#411, #413) +- Meta: Main changelog refactored (#412) + ### [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), 02/07/2019 - Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) - Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) diff --git a/README.md b/README.md index 886104b25..c0e841f06 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ > Having problems with traffic despawning after updating roads or rails? Try [Broken Node Detector](https://steamcommunity.com/sharedfiles/filedetails/?id=1777173984) which helps detect a game bug. Collossal Order are aware of the issue. +#### Version [10.21.1 hotfix](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.21...10.21.1), 06/07/2019 + +- Fixed: Speed panel tanks fps if train tracks on screen (thanks rlas & DaEgi01!) (#411, #413) +- Meta: Main changelog refactored (#412) + #### Version [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), 02/07/2019 - Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) - Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) From 381bdb58cba36af12b2b8e3284a7ed6efb3113ee Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 7 Jul 2019 00:08:27 +0200 Subject: [PATCH 138/142] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9872dff48..2d55935b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ > * **TMPlus** = Traffic Manager Plus > * **TM** = Traffic Manager -#### TM:PE [10.21.1 hotfix](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.21...10.21.1), 06/07/2019 +### TM:PE [10.21.1 hotfix](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.21...10.21.1), 06/07/2019 - Fixed: Speed panel tanks fps if train tracks on screen (thanks rlas & DaEgi01!) (#411, #413) - Meta: Main changelog refactored (#412) From 613a3b607ff07f0acb65a4243c216461f10ba701 Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Tue, 9 Jul 2019 00:41:44 +0100 Subject: [PATCH 139/142] Added complete history of Traffic++ It's features such as Improved AI (aka Advanced Vehicle AI and DLS in TM:PE) and lane connections are now part of TM:PE. --- CHANGELOG.md | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 322 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d55935b6..96b255ecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Cities: Skylines - Traffic Manager: *President Edition* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) +# Cities: Skylines - Traffic Manager: *President Edition* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) # Changelog @@ -6,7 +6,10 @@ > > * **C:SL** = Cities: Skylines game updates > * **TM:PE** = Traffic Manager: President Edition +> * **TPP2** = Traffic++ V2 > * **TMPlus** = Traffic Manager Plus +> * **TPP:AI** - Traffic++ Improved AI +> * **TPP** = Traffic++ > * **TM** = Traffic Manager ### TM:PE [10.21.1 hotfix](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.21...10.21.1), 06/07/2019 @@ -624,6 +627,13 @@ - Fixed: Mod crashed when loading a second savegame - Updated: translations: Polish, Chinese (simplified) +### TPP2 2.0.12, 10/06/2016 + +- Updated: Compatible with C:SL 1.6.0-f4 +- Meta: This was the final release of TPP2 +- Meta: TM:PE continued as the main traffic mod for the game +- Meta: The TPP/TPP2 can still be found in the Network Extensions 2 project + ### TM:PE 1.8.0, 29/11/2016 - Added: Default speed limits @@ -816,6 +826,10 @@ - Fixed: Timed traffic lights UI not working as expected (thanks to Madgemade for pointing out this problem) +### TPP2 2.0.11, 10/06/2016 + +- Updated: Compatible with C:SL 1.5.0-f4 + ### TM:PE 1.6.18, 09/06/2016 - Improved: Players can now select elevated rail segments/nodes @@ -829,6 +843,14 @@ - Added: Football stadium (causes heavy traffic in city on match day) - Fixed: Various bugs in game and DLCs +### TPP2 2.0.10, 02/06/2016 + +- Fixed: NullPointerException + +### TPP2 2.0.9, 31/05/2016 + +- Update: Compatbile with Network Extensions 2.5 + ### TM:PE 1.6.17, 20/04/2016 - Fixed: Hotfix for reported path-finding problems @@ -842,6 +864,12 @@ - Fixed: Busses could enter/exit bus stations at highways with sound barriers - Fixed: Various other bugs +### TPP2 2.0.8, 22/03/2016 + +- Updated: Cpde from TM:PE 1.6.10 +- Updated: Compatible with C:SL 1.4.0-f3 +- Removed: Old code from TPP + ### TM:PE 1.6.15, 22/03/2016 - Added: Traditional Chinese translation @@ -898,12 +926,24 @@ - Fixed: Short roads warning - Fixed: Cyclists despawn when changing from bike path to bike lane +### TPP2 2.0.7, 01/03/2016 + +- Improved: Performance +- Updated: Vehicle AIs +- Updated: Code from TM:PE 1.16 merged in to TPP2 + ### TM:PE 1.6.8, 01/03/2016 - Added: Spanish translation - Improved: Major path-finding performance improvements - Updated: Japanese translation (thanks to Akira Ishizaki for translating!) +### TPP2 2.0.6, 28/02/2016 + +- Updated: Cargo trucks pathfinder +- Updated: Stations and ports pathfinder +- Updated: Code from TM:PE 1.6.6 & 1.6.7 merged in to TPP2 + ### TM:PE 1.6.7, 27/02/2016 - Improved: Tuned AI parameters @@ -924,11 +964,41 @@ - Updated: Portuguese translation (thanks to igordeeoliveira for translating!) - Updated: Russian translation (thanks to FireGames for translating!) +### TPP2 2.0.5, 26/02/2016 + +- Improved: Service vehicle pathfinding +- Fixed: Cars disappearing underground + +### TPP2 2.0.4, 24/02/2016 + +- Improved: Vehicle restrictions +- Updated: Removed typecasting from Car AI +- Updated: Major pathfinder refactor +- Updated: Code from TM:PE 1.6.0 to 1.6.5 merged in to TPP2 + ### TM:PE 1.6.5, 24/02/2016 - Added: Despawning setting to options dialog - Improved: Detection of Traffic++ V2 +### TPP2 2.0.3, 23/02/2016 + +- Added: Medium pedestrianised road +- Fixed: Busses not using custom pathfinder +- Fixed: Crash bug when Snowfall DLC subscribed +- Fixed: Trams not working +- Fixed: Transport lines not respecting restrictions +- Fixed: Broken bus lanes on roads +- Fixed: Path finder not respecting vehicle types +- Fixed: Car AI broken when using Snowfall DLC +- Fixed: Bug loading options +- Updated: Improved how extended vehicle types are defined +- Updated: Custom path manager refactored +- Updated: Namespace and project refactor & clean-up +- Updated: Separate extensions and AIs +- Updated: Imrpoved options definition and storage +- Removed: Ghost Mode + ### TM:PE 1.6.4, 23/02/2016 - Improved: Minor performance improvements @@ -945,10 +1015,17 @@ ### TM:PE 1.6.3, 22/02/2016 -- Fixed: Using the "Old Town" policy led to vehicles not spawning. +- Fixed: Using the "Old Town" policy led to vehicles not spawning - Fixed: Planes, cargo trains and ship were sometimes not arriving - Fixed: Trams are not doing u-turns anymore +### TPP2 2.0.2, 21/02/2016 + +- Fixed: Bus routing & bus stops +- Fixed: European theme incompatible (thanks BloodyPenguin for help!) +- Fixed: Traffic doesn't stop for flooded tunnels +- Updated: Serialise for lane data + ### TM:PE 1.6.2, 20/02/2016 - Added: Trams are now obeying speed limits (thanks to Clausewitz for pointing out the issue) @@ -962,6 +1039,22 @@ - Improved: Modifying mod options through the main menu now gives an annoying warning message instead of a blank page. - Fixed: Various UI issues +### TPP2 2.0.1, 19/02/2016 + +- Fixed: Default vehicle restrictions always include bus +- Updated: Compatible with C:SL 1.3.0-f4 + +### TPP2 2.0.0, 18/02/2016 + +- Added: Allow restrictions on pedestrianised roads +- Fixed: Bus lines not working +- Fixed: Traffic++ roads +- Fixed: Options screen now saves options properly +- Improved: Better compatibility with TPP +- Improved: Detection of incompatible mods +- Updated: Lots of code clean-up +- Meta: First sable release of TPP2 + ### TM:PE 1.6.0, 18/02/2016 - Added: Separate traffic lights for different vehicle types @@ -993,6 +1086,23 @@ - Added: Road conditions affect vehicle speed - Added: Priority routes +### TPP2 0.0.1, 17/02/2016 + +- Added: Tool - Lane Connector (from TPP) +- Added: Tool - Vehicle Restrictions (from TPP) +- Added: Tool - Speed Restrictions (from TPP) +- Added: No Despawn (from TPP) +- Added: Improved AI (from TPP:AI) +- Meta: Released as version 0.0a + +### TPP2 0.0, 17/02/2016 + +- Meta: Development started 4th October 2015 +- Meta: Was planned to replace TM:PE, TPP, etc +- Meta: Dev team: LinuxFan, Katalyst6, Lazarus*Man +- Meta: GitHub Repository: [Katalyst6/CSL.TransitAddonMod](https://github.com/Katalyst6/CSL.TransitAddonMod) +- Meta: Steam Workshop: [626024868 - Traffic++ V2](https://steamcommunity.com/sharedfiles/filedetails/?id=626024868) + ### TM:PE 1.5.2, 01/02/2016 - Added: Traffic lights may now be added to/removed from underground junctions @@ -1273,6 +1383,27 @@ - Fixed: Minor bugfixes with game and asset editor +### TPP 1.6.1, 25/09/2015 + +- Removed: In-game log messages +- Meta: This was the last release of Traffic++ mod +- Meta: It was later continued in Traffic++ V2 (TPP2) +- Meta: It's features have since been merged in to TM:PE (tools & AIs) and NExt2 (roads) + +### TPP 1.6.0, 25/09/2015 + +- Fixed: Rendering of road customiser in underground view +- Updated: Integrated latest Improved Vehicle AI +- Updated: Lamps on pedestrian roads + +### TPP 1.5.6, 24/09/2015 + +- Fixed: Disable custom roads option not working + +### TPP 1.5.5, 24/09/2015 + +- Update: Compatible with C:SL 1.2.0 (thanks javitonino) + ### C:SL 1.2 (After Dark), 24/09/2015 - Added: Bicycles @@ -1297,6 +1428,15 @@ - Updated: New load system - Meta: Lots of code refactoring +### TPP:AI 1.0.0, 02/08/2015 + +- Added: Improved Vehicle AI +- Meta: This was a preview mod by Jfarias, developer of TPP +- Meta: It made vehicles use more lanes, and also change lanes (later becoming "DLS" in TM:PE) +- Meta: Steam Workshop: [492391912 - Improved AI Traffic++](https://steamcommunity.com/sharedfiles/filedetails/?id=492391912) +- Meta: Source code no longer avialable +- Meta: The feature was merged in to later version of TPP and, later, TM:PE (Advanced Vehicle AI and DLS) + ### TM 1.0.5, 28/07/2015 - Fixed: Pathfinder bugs @@ -1331,6 +1471,10 @@ - Meta: Project fork by sieggy - Meta: First release in Steam Workshop under name "Traffic Manager Plus" +### TPP 1.5.4, 06/07/2015 + +- Updated: Compatible with C:SL 1.1.1 + ### C:SL 1.1.1, 01/07/2015 - Added: Tunnels for pedestrian paths @@ -1338,23 +1482,109 @@ - Fixed: Lots of stuff - Improved: Asset editor +### TPP 1.5.3, 02/06/2015 + +- Fixed: Crash on Linux + +### TPP 1.5.2, 02/06/2015 + +- Fixed: Trucks not allowed on new roads + +### TPP 1.5.1, 01/06/2015 + +- Fixed: Bug preventing options loading if button not shown in content manager +- Fixed: Duplicate prefab bug in Ghost Mode +- Fixed: Duplicate roads in roads panel in Ghost Mode +- Fixed: Vehicles have wrong AI when returning to main menu +- Fixed: Vehicles using restricted lanes +- Fixed: Bug preventing the Scrollable Toolbar mod from working in the entirety of the speed customizer panel +- Updated: Added debug logs to the game's Debug Panel (F7) - thanks to Nefarion for implementing it + ### TM 1.0.4, 29/05/2015 - Fixed: Negative timers (thanks XaBBoK!) - Improved: Pathfinder cleanup (thanks dornathal!) +### TPP 1.5.0 hotfix, 28/05/2015 + +- Fixed: Vehicles stopping in road +- Fixed: Realistic speeds not stopping without restarting the game +- Fixed: Road costs (thanks Archomeda) + +### TPP 1.5.0, 27/05/2015 + +- Added: More roads +- Added: Option for Improved Vehicle AI +- Fixed: Bug cauing crashes when loading map through pause menu +- Fixed: Tunnels from new roads turning in to normal tunnels +- Fixed: Custom vehicles ignoring lane restricitons + +### TPP 1.4.0 hotfix, 23/05/2015 + +- Fixed: Few more bugs + +### TPP 1.4.0 hotfix, 23/05/2015 + +- Fixed: Wrong lane usage in highways + +### TPP 1.4.0, 22/05/2015 + +- Added: Tool - Speed Limits +- Added: Underground view for customisation tools +- Added: Mod Option - No Despawn (from TM:PE) +- Added: Multi-track station enabler +- Improved: New UI for road customisation tools +- Fixed: Road customisation tools appearing in asset editor +- Fixed: Objects under the map bug +- Fixed: Options button on all resolutions +- Fixed: Train tracks should not be selectable + +### TPP 1.3.2 hotfix, 20/05/2015 + +- Improved: Compatibility with C:SL 1.1.0b + +### TPP 1.3.2 hotfix, 19/05/2015 + +- Update: Compatible with C:SL 1.1.0b + ### TM 1.0.4 hotfix, 19/05/2015 - Fixed: Support for tunnels in AIs and pathfinder -- Update: Compatible with C:SL 1.1 +- Update: Compatible with C:SL 1.1.0b - Update: Traffic++ Compatibility - No despawn or lane changer available in compatibility mode -### C:SL 1.1, 19/05/2015 +### C:SL 1.1.0b, 19/05/2015 - Added: European theme - Added: Tunnels for roads and rail - Improved: Metro tunnels can be built at different heights +### TPP 1.3.2, 05/05/2015 + +- Added: Ability to set restrictions lane by lane +- Added: Ability to customise multiple lanes at same time +- Fixed: But that prevented settings saving +- Fixed: Bug that prevented short roads being selected +- Fixed: Vehicle restrictions now show correctly on all resolutions +- Updated: Road customiser tool button moved +- Updated: Customisation overlays visible at all times when tool active + +### TPP 1.3.1, 29/04/2015 + +- Improved: Perfomance issues reduced +- Fixed: Strange vehicle behaviours +- Fixed: Prevent road tool from selecting vehicles + +### TPP 1.3.0, 28/04/2015 + +- Added: More roads +- Added: Services overlay (vehicle restrictions) for pedestrian roads +- Added: Tool - Vehicle Restrictions +- Added: Tool - Lane Connections +- Fixed: Left hand traffic issues +- Fixed: Ghost mode caused crash if busway bridges on map +- Fixed: Several crashes caused by mod incompatibilities + ### TM 1.0.4 beta, 22/04/2015 - Added: Buttons for timed traffic light states: skip and move up/down @@ -1447,14 +1677,75 @@ - Updated: Better traffic light UI - Meta: Using cities-skylines-detour by cope (sschoener on github) +### TPP 1.2.0, 14/04/2015 + +- Added: Realistic driving speeds +- Added: More roads +- Fixed: Bug in mod options screen +- Updated: Compatible with C:SL 1.0.7c + ### C:SL 1.0.7c, 07/04/2015 - Fixed: Lots of stuff +### TPP 1.2.1, 06/04/2015 + +- Fixed: Bugs introduced by previous version + +### TPP 1.1.8, 05/04/2015 + +- Added: Support for left-hand traffic maps +- Improved: Compatibility with Fine Road Heights mod +- Fixed: Various bugs +- Updated: Compatible with C:SL 1.0.7 + ### C:SL 1.0.7, 27/03/2015 - Fixed: Lots of stuff +### TPP 1.1.7, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.6, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.5, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.4, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.3, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.2, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.1, 25/03/2015 + +- Added: Option to disable updates from previous version +- Fixed: Bugs from previous version + +### TPP 1.1.0, 25/03/2015 + +- Added: More roads & associated features +- Added: Option to disable pedestrians in middle lane +- Meta: Renamed to "Traffic++" + +### TPP 1.0.5, 24/03/2015 + +- Fixed: Options panel bug + +### TPP 1.0.4, 23/03/2015 + +- Fixed: Various bugs + ### TM 0.5.0, 22/03/2015 - Added: Tool - Traffic Lights Editor @@ -1463,14 +1754,39 @@ - Added: Log file - Updated: Compatible with C:SL 1.0.6 +### TPP 1.0.3, 20/03/2015 + +- Added: Option to toggle which vehicles can use pedestrian roads +- Added: Ghost mode to disable most of mod but still allow maps to load +- Updated: Compatible with C:SL 1.0.6 +- Meta: First working implementation of vehicle restrictions + ### C:SL 1.0.6, 19/03/2015 - Fixed: Lots of stuff +### TPP 1.0.2, 18/03/2015 + +- Improved: Some interface improvements + +### TPP 1.0.1, 17/03/2015 + +- Fixed: Mod could only be used once per gaming session +- Fixed: Various bugs + +### TPP 1.0.0, 16/03/2015 + +- Added: Pedestrian zoneable road +- Meta: Traffic++ project starts, but under its original name "CSL-Traffic" +- Meta: GitHub Repository: [joaofarias/csl-traffic](https://github.com/joaofarias/csl-traffic) +- Meta: Steam Workshop + ### TM 0.4.0, 14/03/2015 -- Meta: Traffic Manager project officially started (at v0.4 for some reason!?) -- Meta: Github repository created by CBeTHaX (github user SvetlozarValchev) +- Meta: Traffic Manager project starts +- Meta: Developer CBeTHaX (github user SvetlozarValchev) +- Meta: GitHub Repository: [SvetlozarValchev/Skylines-Traffic-Manager](https://github.com/SvetlozarValchev/Skylines-Traffic-Manager) +- Meta: Steam Workshop: Traffic Manager ### C:SL 1.0, 10/03/2015 From 9b3996ce9d0e7cbf0899ed2f8ecc1e3b3fe5b48e Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 15 Jul 2019 01:19:20 +0200 Subject: [PATCH 140/142] Moved type and old legacy type given different namespaces --- TLM/TLM/Custom/AI/CustomAmbulanceAI.cs | 150 +- TLM/TLM/Custom/AI/CustomBusAI.cs | 142 +- TLM/TLM/Custom/AI/CustomCarAI.cs | 1121 ++-- TLM/TLM/Custom/AI/CustomCargoTruckAI.cs | 266 +- TLM/TLM/Custom/AI/CustomFireTruckAI.cs | 155 +- TLM/TLM/Custom/AI/CustomHumanAI.cs | 3 + TLM/TLM/Custom/AI/CustomPoliceCarAI.cs | 147 +- TLM/TLM/Custom/AI/CustomPostVanAI.cs | 211 +- TLM/TLM/Custom/AI/CustomShipAI.cs | 150 +- TLM/TLM/Custom/AI/CustomTaxiAI.cs | 164 +- TLM/TLM/Custom/AI/CustomTrainAI.cs | 1705 ++--- TLM/TLM/Custom/AI/CustomTramBaseAI.cs | 1384 ++-- TLM/TLM/Custom/AI/CustomTransportLineAI.cs | 375 +- TLM/TLM/Custom/PathFinding/CustomPathFind.cs | 5211 +++++++-------- TLM/TLM/Custom/PathFinding/CustomPathFind2.cs | 5836 +++++++++-------- .../Custom/PathFinding/CustomPathManager.cs | 2 + .../AbstractGeometryObservingManager.cs | 189 +- .../Manager/Impl/AdvancedParkingManager.cs | 3300 +++++----- .../Impl/CustomSegmentLightsManager.cs | 570 +- .../Manager/Impl/ExtCitizenInstanceManager.cs | 2048 +++--- TLM/TLM/Manager/Impl/ExtCitizenManager.cs | 2 + TLM/TLM/Manager/Impl/ExtVehicleManager.cs | 1263 ++-- TLM/TLM/Manager/Impl/LaneArrowManager.cs | 311 +- TLM/TLM/Manager/Impl/LaneConnectionManager.cs | 1149 ++-- TLM/TLM/Manager/Impl/ManagerFactory.cs | 2 + TLM/TLM/Manager/Impl/OptionsManager.cs | 2 + TLM/TLM/Manager/Impl/RoutingManager.cs | 2 + TLM/TLM/Manager/Impl/TrafficLightManager.cs | 2 + .../Impl/TrafficLightSimulationManager.cs | 1181 ++-- .../Manager/Impl/TrafficPriorityManager.cs | 1983 +++--- .../Manager/Impl/VehicleBehaviorManager.cs | 3580 +++++----- .../Impl/VehicleRestrictionsManager.cs | 1142 ++-- TLM/TLM/Properties/AssemblyInfo.cs | 2 +- TLM/TLM/State/ConfigData/Debug.cs | 108 +- .../State/ConfigData/TimedTrafficLights.cs | 2 + TLM/TLM/State/Configuration.cs | 601 +- TLM/TLM/State/Flags.cs | 1778 +++-- TLM/TLM/State/Options.cs | 1 + TLM/TLM/TLM.csproj | 1 + TLM/TLM/Traffic/ExtVehicleType.cs | 53 + TLM/TLM/Traffic/Impl/SegmentEnd.cs | 3 + TLM/TLM/TrafficLight/Impl/CustomSegment.cs | 4 +- .../TrafficLight/Impl/CustomSegmentLight.cs | 2 + .../TrafficLight/Impl/CustomSegmentLights.cs | 1149 ++-- .../TrafficLight/Impl/TimedTrafficLights.cs | 1975 +++--- .../Impl/TimedTrafficLightsStep.cs | 1875 +++--- TLM/TLM/UI/SubTools/LaneArrowTool.cs | 2 + .../UI/SubTools/ManualTrafficLightsTool.cs | 1398 ++-- TLM/TLM/UI/SubTools/PrioritySignsTool.cs | 2 + TLM/TLM/UI/SubTools/SpeedLimitsTool.cs | 1312 ++-- TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs | 3438 +++++----- .../UI/SubTools/ToggleTrafficLightsTool.cs | 2 + .../UI/SubTools/VehicleRestrictionsTool.cs | 753 +-- TLM/TLM/UI/TextureResources.cs | 556 +- TLM/TLM/UI/TrafficManagerTool.cs | 41 +- TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs | 2 + TLM/TMPE.API/Geometry/ISegmentGeometry.cs | 2 + .../Manager/IAdvancedParkingManager.cs | 2 + .../Manager/ICustomSegmentLightsManager.cs | 40 +- TLM/TMPE.API/Manager/IExtVehicleManager.cs | 326 +- TLM/TMPE.API/Manager/IManagerFactory.cs | 2 + TLM/TMPE.API/Manager/ITrafficLightManager.cs | 2 + .../Manager/ITrafficPriorityManager.cs | 2 + .../Manager/IVehicleBehaviorManager.cs | 2 + .../Manager/IVehicleRestrictionsManager.cs | 89 +- TLM/TMPE.API/TMPE.API.csproj | 2 +- TLM/TMPE.API/Traffic/Data/ExtCitizen.cs | 2 + .../Traffic/Data/ExtCitizenInstance.cs | 2 + TLM/TMPE.API/Traffic/Data/ExtVehicle.cs | 195 +- TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs | 224 +- .../Traffic/Data/PathUnitQueueItem.cs | 8 +- TLM/TMPE.API/Traffic/Data/PrioritySegment.cs | 2 + TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs | 51 +- .../Traffic/Enums/EmergencyBehavior.cs | 7 +- .../Traffic/Enums/ExtParkingSpaceLocation.cs | 37 +- TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs | 163 +- TLM/TMPE.API/Traffic/Enums/ExtPathState.cs | 45 +- TLM/TMPE.API/Traffic/Enums/ExtPathType.cs | 7 +- .../Traffic/Enums/ExtSoftPathState.cs | 61 +- .../Traffic/Enums/ExtTransportMode.cs | 39 +- TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs | 23 +- TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs | 75 +- .../Traffic/Enums/FlowWaitCalcMode.cs | 31 +- .../Traffic/Enums/GeometryCalculationMode.cs | 19 +- TLM/TMPE.API/Traffic/Enums/LaneArrows.cs | 39 +- .../Traffic/Enums/LaneEndTransitionType.cs | 45 +- TLM/TMPE.API/Traffic/Enums/LightMode.cs | 7 +- .../Traffic/Enums/ParkedCarApproachState.cs | 51 +- .../Traffic/Enums/ParkingUnableReason.cs | 43 +- TLM/TMPE.API/Traffic/Enums/PriorityType.cs | 39 +- .../Traffic/Enums/SetLaneArrowUnableReason.cs | 21 +- .../Enums/SetPrioritySignUnableReason.cs | 23 +- .../Traffic/Enums/StepChangeMetric.cs | 53 +- .../Enums/ToggleTrafficLightUnableReason.cs | 23 +- .../Enums/TrafficLightSimulationType.cs | 19 +- .../Enums/VehicleJunctionTransitState.cs | 46 +- .../Enums/VehicleRestrictionsAggression.cs | 53 +- .../Traffic/Enums/VehicleRestrictionsMode.cs | 37 +- .../Data/TrafficLightSimulation.cs | 3 + .../TrafficLight/ICustomSegmentLight.cs | 2 + .../TrafficLight/ICustomSegmentLights.cs | 82 +- .../TrafficLight/ITimedTrafficLights.cs | 84 +- .../TrafficLight/ITimedTrafficLightsStep.cs | 76 +- 103 files changed, 25466 insertions(+), 25548 deletions(-) create mode 100644 TLM/TLM/Traffic/ExtVehicleType.cs diff --git a/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs b/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs index a71b85999..c1be31520 100644 --- a/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs +++ b/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs @@ -1,81 +1,75 @@ -using ColossalFramework; -using TrafficManager.RedirectionFramework.Attributes; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(AmbulanceAI))] - public class CustomAmbulanceAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); + [TargetType(typeof(AmbulanceAI))] + public class CustomAmbulanceAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); - VehicleInfo info = this.m_info; - bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startDistSqrA; - float startDistSqrB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endDistSqrA; - float endDistSqrB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && - CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = vehicleType; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + VehicleInfo info = this.m_info; + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startDistSqrA; + float startDistSqrB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endDistSqrA; + float endDistSqrB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && + CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = vehicleType; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } else { - PathfindFailure(vehicleID, ref vehicleData); - } - return false; - } - } -} + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } else { + PathfindFailure(vehicleID, ref vehicleData); + } + return false; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomBusAI.cs b/TLM/TLM/Custom/AI/CustomBusAI.cs index 3bcc2ed49..ae897d40a 100644 --- a/TLM/TLM/Custom/AI/CustomBusAI.cs +++ b/TLM/TLM/Custom/AI/CustomBusAI.cs @@ -1,77 +1,71 @@ -using ColossalFramework; -using TrafficManager.RedirectionFramework.Attributes; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(BusAI))] - public class CustomBusAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - VehicleInfo info = this.m_info; - bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startDistSqrA; - float startDistSqrB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endDistSqrA; - float endDistSqrB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && - CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = ExtVehicleType.Bus; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.randomParking = false; - args.ignoreCosts = false; - args.stablePath = true; - args.skipQueue = true; + [TargetType(typeof(BusAI))] + public class CustomBusAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + VehicleInfo info = this.m_info; + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startDistSqrA; + float startDistSqrB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endDistSqrA; + float endDistSqrB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && + CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = ExtVehicleType.Bus; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.randomParking = false; + args.ignoreCosts = false; + args.stablePath = true; + args.skipQueue = true; - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } - } -} + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomCarAI.cs b/TLM/TLM/Custom/AI/CustomCarAI.cs index 984623987..4ce38a029 100644 --- a/TLM/TLM/Custom/AI/CustomCarAI.cs +++ b/TLM/TLM/Custom/AI/CustomCarAI.cs @@ -1,609 +1,610 @@ #define DEBUGVx -using System; -using System.Collections.Generic; -using ColossalFramework; -using ColossalFramework.Math; -using TrafficManager.Geometry; -using TrafficManager.TrafficLight; -using UnityEngine; -using Random = UnityEngine.Random; -using TrafficManager.Custom.PathFinding; -using TrafficManager.State; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using CSUtil.Commons; -using TrafficManager.Manager.Impl; -using System.Runtime.CompilerServices; -using TrafficManager.Traffic.Data; -using static TrafficManager.Traffic.Data.ExtCitizenInstance; -using CSUtil.Commons.Benchmark; -using static TrafficManager.Custom.PathFinding.CustomPathManager; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - namespace TrafficManager.Custom.AI { - [TargetType(typeof(CarAI))] - public class CustomCarAI : CarAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) - public void Awake() { - - } - - /// - /// Lightweight simulation step method. - /// This method is occasionally being called for different cars. - /// - /// - /// - /// - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { + using System; + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using Traffic.Data; + using UnityEngine; + + [TargetType(typeof(CarAI))] + public class CustomCarAI : CarAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) + public void Awake() { + + } + + /// + /// Lightweight simulation step method. + /// This method is occasionally being called for different cars. + /// + /// + /// + /// + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { #if DEBUG - bool vehDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId); - bool debug = GlobalConfig.Instance.Debug.Switches[2] && vehDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && vehDebug; + bool vehDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId); + bool debug = GlobalConfig.Instance.Debug.Switches[2] && vehDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && vehDebug; #endif - if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) { - PathManager pathManager = Singleton.instance; - byte pathFindFlags = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags; + if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) { + PathManager pathManager = Singleton.instance; + byte pathFindFlags = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags; - // NON-STOCK CODE START - ExtPathState mainPathState = ExtPathState.Calculating; - if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) { - mainPathState = ExtPathState.Failed; - } else if ((pathFindFlags & PathUnit.FLAG_READY) != 0) { - mainPathState = ExtPathState.Ready; - } + // NON-STOCK CODE START + ExtPathState mainPathState = ExtPathState.Calculating; + if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) { + mainPathState = ExtPathState.Failed; + } else if ((pathFindFlags & PathUnit.FLAG_READY) != 0) { + mainPathState = ExtPathState.Ready; + } #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path: {vehicleData.m_path}, mainPathState={mainPathState}"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path: {vehicleData.m_path}, mainPathState={mainPathState}"); #endif - IExtVehicleManager extVehicleManager = Constants.ManagerFactory.ExtVehicleManager; - ExtSoftPathState finalPathState = ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - if (Options.parkingAI && extVehicleManager.ExtVehicles[vehicleId].vehicleType == ExtVehicleType.PassengerCar) { - ushort driverInstanceId = extVehicleManager.GetDriverInstanceId(vehicleId, ref vehicleData); - finalPathState = AdvancedParkingManager.Instance.UpdateCarPathState(vehicleId, ref vehicleData, ref Singleton.instance.m_instances.m_buffer[driverInstanceId], ref ExtCitizenInstanceManager.Instance.ExtInstances[driverInstanceId], mainPathState); + IExtVehicleManager extVehicleManager = Constants.ManagerFactory.ExtVehicleManager; + ExtSoftPathState finalPathState = ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + if (Options.parkingAI && extVehicleManager.ExtVehicles[vehicleId].vehicleType == ExtVehicleType.PassengerCar) { + ushort driverInstanceId = extVehicleManager.GetDriverInstanceId(vehicleId, ref vehicleData); + finalPathState = AdvancedParkingManager.Instance.UpdateCarPathState(vehicleId, ref vehicleData, ref Singleton.instance.m_instances.m_buffer[driverInstanceId], ref ExtCitizenInstanceManager.Instance.ExtInstances[driverInstanceId], mainPathState); #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Applied Parking AI logic. Path: {vehicleData.m_path}, mainPathState={mainPathState}, finalPathState={finalPathState}"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Applied Parking AI logic. Path: {vehicleData.m_path}, mainPathState={mainPathState}, finalPathState={finalPathState}"); #endif - } + } - switch (finalPathState) { - case ExtSoftPathState.Ready: + switch (finalPathState) { + case ExtSoftPathState.Ready: #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding succeeded for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.PathfindSuccess"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding succeeded for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.PathfindSuccess"); #endif - vehicleData.m_pathPositionIndex = 255; - vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; - vehicleData.m_flags &= ~Vehicle.Flags.Arriving; - this.PathfindSuccess(vehicleId, ref vehicleData); - this.TrySpawn(vehicleId, ref vehicleData); - break; - case ExtSoftPathState.Ignore: + vehicleData.m_pathPositionIndex = 255; + vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; + vehicleData.m_flags &= ~Vehicle.Flags.Arriving; + this.PathfindSuccess(vehicleId, ref vehicleData); + this.TrySpawn(vehicleId, ref vehicleData); + break; + case ExtSoftPathState.Ignore: #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding result shall be ignored for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- ignoring"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding result shall be ignored for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- ignoring"); #endif - return; - case ExtSoftPathState.Calculating: - default: + return; + case ExtSoftPathState.Calculating: + default: #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding result undetermined for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- continue"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding result undetermined for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- continue"); #endif - break; - case ExtSoftPathState.FailedHard: + break; + case ExtSoftPathState.FailedHard: #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): HARD path-finding failure for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.PathfindFailure"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): HARD path-finding failure for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.PathfindFailure"); #endif - vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; - Singleton.instance.ReleasePath(vehicleData.m_path); - vehicleData.m_path = 0u; - this.PathfindFailure(vehicleId, ref vehicleData); - return; - case ExtSoftPathState.FailedSoft: + vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; + Singleton.instance.ReleasePath(vehicleData.m_path); + vehicleData.m_path = 0u; + this.PathfindFailure(vehicleId, ref vehicleData); + return; + case ExtSoftPathState.FailedSoft: #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): SOFT path-finding failure for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.InvalidPath"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): SOFT path-finding failure for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.InvalidPath"); #endif - // path mode has been updated, repeat path-finding - vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; - this.InvalidPath(vehicleId, ref vehicleData, vehicleId, ref vehicleData); - break; - } - // NON-STOCK CODE END - } else { - if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) { - this.TrySpawn(vehicleId, ref vehicleData); - } - } - - // NON-STOCK CODE START - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); - - if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { - extVehicleMan.LogTraffic(vehicleId, ref vehicleData); - } - // NON-STOCK CODE END - - Vector3 lastFramePosition = vehicleData.GetLastFramePosition(); - int lodPhysics; - if (Vector3.SqrMagnitude(physicsLodRefPos - lastFramePosition) >= 1210000f) { - lodPhysics = 2; - } else if (Vector3.SqrMagnitude(Singleton.instance.m_simulationView.m_position - lastFramePosition) >= 250000f) { - lodPhysics = 1; - } else { - lodPhysics = 0; - } - this.SimulationStep(vehicleId, ref vehicleData, vehicleId, ref vehicleData, lodPhysics); - if (vehicleData.m_leadingVehicle == 0 && vehicleData.m_trailingVehicle != 0) { - VehicleManager vehManager = Singleton.instance; - ushort trailerId = vehicleData.m_trailingVehicle; - int numIters = 0; - while (trailerId != 0) { - ushort trailingVehicle = vehManager.m_vehicles.m_buffer[(int)trailerId].m_trailingVehicle; - VehicleInfo info = vehManager.m_vehicles.m_buffer[(int)trailerId].Info; - info.m_vehicleAI.SimulationStep(trailerId, ref vehManager.m_vehicles.m_buffer[(int)trailerId], vehicleId, ref vehicleData, lodPhysics); - trailerId = trailingVehicle; - if (++numIters > 16384) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } - - int privateServiceIndex = ItemClass.GetPrivateServiceIndex(this.m_info.m_class.m_service); - int maxBlockCounter = (privateServiceIndex == -1) ? 150 : 100; - if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace)) == 0 && vehicleData.m_cargoParent == 0) { - Singleton.instance.ReleaseVehicle(vehicleId); - } else if ((int)vehicleData.m_blockCounter >= maxBlockCounter) { - // NON-STOCK CODE START - if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { - // NON-STOCK CODE END - Singleton.instance.ReleaseVehicle(vehicleId); - } // NON-STOCK CODE - } - } - - [RedirectMethod] - public bool CustomTrySpawn(ushort vehicleId, ref Vehicle vehicleData) { - if ((vehicleData.m_flags & Vehicle.Flags.Spawned) != (Vehicle.Flags)0) { - return true; - } - if (CustomCarAI.CheckOverlap(vehicleData.m_segment, 0, 1000f)) { - vehicleData.m_flags |= Vehicle.Flags.WaitingSpace; - return false; - } - vehicleData.Spawn(vehicleId); - vehicleData.m_flags &= ~Vehicle.Flags.WaitingSpace; - return true; - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextNextPosition, - PathUnit.Position nextPosition, uint nextLaneId, byte nextOffset, PathUnit.Position curPosition, uint curLaneId, - byte curOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) { - var netManager = Singleton.instance; - ushort nextSourceNodeId; - ushort nextTargetNodeId; - if (nextOffset < nextPosition.m_offset) { - nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; - nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; - } else { - nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; - nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; - } - - ushort curTargetNodeId; - if (curOffset == 0) { - curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_startNode; - } else { - curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_endNode; - } + // path mode has been updated, repeat path-finding + vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; + this.InvalidPath(vehicleId, ref vehicleData, vehicleId, ref vehicleData); + break; + } + // NON-STOCK CODE END + } else { + if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) { + this.TrySpawn(vehicleId, ref vehicleData); + } + } + + // NON-STOCK CODE START + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); + + if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { + extVehicleMan.LogTraffic(vehicleId, ref vehicleData); + } + // NON-STOCK CODE END + + Vector3 lastFramePosition = vehicleData.GetLastFramePosition(); + int lodPhysics; + if (Vector3.SqrMagnitude(physicsLodRefPos - lastFramePosition) >= 1210000f) { + lodPhysics = 2; + } else if (Vector3.SqrMagnitude(Singleton.instance.m_simulationView.m_position - lastFramePosition) >= 250000f) { + lodPhysics = 1; + } else { + lodPhysics = 0; + } + this.SimulationStep(vehicleId, ref vehicleData, vehicleId, ref vehicleData, lodPhysics); + if (vehicleData.m_leadingVehicle == 0 && vehicleData.m_trailingVehicle != 0) { + VehicleManager vehManager = Singleton.instance; + ushort trailerId = vehicleData.m_trailingVehicle; + int numIters = 0; + while (trailerId != 0) { + ushort trailingVehicle = vehManager.m_vehicles.m_buffer[(int)trailerId].m_trailingVehicle; + VehicleInfo info = vehManager.m_vehicles.m_buffer[(int)trailerId].Info; + info.m_vehicleAI.SimulationStep(trailerId, ref vehManager.m_vehicles.m_buffer[(int)trailerId], vehicleId, ref vehicleData, lodPhysics); + trailerId = trailingVehicle; + if (++numIters > 16384) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } + + int privateServiceIndex = ItemClass.GetPrivateServiceIndex(this.m_info.m_class.m_service); + int maxBlockCounter = (privateServiceIndex == -1) ? 150 : 100; + if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace)) == 0 && vehicleData.m_cargoParent == 0) { + Singleton.instance.ReleaseVehicle(vehicleId); + } else if ((int)vehicleData.m_blockCounter >= maxBlockCounter) { + // NON-STOCK CODE START + if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { + // NON-STOCK CODE END + Singleton.instance.ReleaseVehicle(vehicleId); + } // NON-STOCK CODE + } + } + + [RedirectMethod] + public bool CustomTrySpawn(ushort vehicleId, ref Vehicle vehicleData) { + if ((vehicleData.m_flags & Vehicle.Flags.Spawned) != (Vehicle.Flags)0) { + return true; + } + if (CustomCarAI.CheckOverlap(vehicleData.m_segment, 0, 1000f)) { + vehicleData.m_flags |= Vehicle.Flags.WaitingSpace; + return false; + } + vehicleData.Spawn(vehicleId); + vehicleData.m_flags &= ~Vehicle.Flags.WaitingSpace; + return true; + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextNextPosition, + PathUnit.Position nextPosition, uint nextLaneId, byte nextOffset, PathUnit.Position curPosition, uint curLaneId, + byte curOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) { + var netManager = Singleton.instance; + ushort nextSourceNodeId; + ushort nextTargetNodeId; + if (nextOffset < nextPosition.m_offset) { + nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; + nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; + } else { + nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; + nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; + } + + ushort curTargetNodeId; + if (curOffset == 0) { + curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_startNode; + } else { + curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_endNode; + } #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[21] && (GlobalConfig.Instance.Debug.NodeId <= 0 || curTargetNodeId == GlobalConfig.Instance.Debug.NodeId) && (GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.None || GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.RoadVehicle) && (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId); - - if (debug) { - Log._Debug($"CustomCarAI.CustomCalculateSegmentPosition({vehicleId}) called.\n" + - $"\tcurPosition.m_segment={curPosition.m_segment}, curPosition.m_offset={curPosition.m_offset}\n" + - $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + - $"\tnextNextPosition.m_segment={nextNextPosition.m_segment}, nextNextPosition.m_offset={nextNextPosition.m_offset}\n" + - $"\tcurLaneId={curLaneId}, curOffset={curOffset}\n" + - $"\tnextLaneId={nextLaneId}, nextOffset={nextOffset}\n" + - $"\tnextSourceNodeId={nextSourceNodeId}, nextTargetNodeId={nextTargetNodeId}\n" + - $"\tcurTargetNodeId={curTargetNodeId}, curTargetNodeId={curTargetNodeId}\n" + - $"\tindex={index}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[21] + && (GlobalConfig.Instance.Debug.NodeId <= 0 + || curTargetNodeId == GlobalConfig.Instance.Debug.NodeId) + && (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.RoadVehicle) + && (GlobalConfig.Instance.Debug.VehicleId == 0 + || GlobalConfig.Instance.Debug.VehicleId == vehicleId); + + if (debug) { + Log._Debug($"CustomCarAI.CustomCalculateSegmentPosition({vehicleId}) called.\n" + + $"\tcurPosition.m_segment={curPosition.m_segment}, curPosition.m_offset={curPosition.m_offset}\n" + + $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + + $"\tnextNextPosition.m_segment={nextNextPosition.m_segment}, nextNextPosition.m_offset={nextNextPosition.m_offset}\n" + + $"\tcurLaneId={curLaneId}, curOffset={curOffset}\n" + + $"\tnextLaneId={nextLaneId}, nextOffset={nextOffset}\n" + + $"\tnextSourceNodeId={nextSourceNodeId}, nextTargetNodeId={nextTargetNodeId}\n" + + $"\tcurTargetNodeId={curTargetNodeId}, curTargetNodeId={curTargetNodeId}\n" + + $"\tindex={index}"); + } #endif - Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); - Vector3 lastFrameVehiclePos = lastFrameData.m_position; - float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; - var prevSegmentInfo = netManager.m_segments.m_buffer[nextPosition.m_segment].Info; - netManager.m_lanes.m_buffer[nextLaneId].CalculatePositionAndDirection(nextOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); - - float braking = this.m_info.m_braking; - if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != (Vehicle.Flags)0) { - braking *= 2f; - } - - // car position on the Bezier curve of the lane - var refVehiclePosOnBezier = netManager.m_lanes.m_buffer[curLaneId].CalculatePosition(curOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - //ushort currentSegmentId = netManager.m_lanes.m_buffer[prevLaneID].m_segment; - - // this seems to be like the required braking force in order to stop the vehicle within its half length. - var crazyValue = 0.5f * sqrVelocity / braking + m_info.m_generatedInfo.m_size.z * 0.5f; - bool withinBrakingDistance = Vector3.Distance(lastFrameVehiclePos, refVehiclePosOnBezier) >= crazyValue - 1f; - - if ( - nextSourceNodeId == curTargetNodeId && - withinBrakingDistance - ) { - // NON-STOCK CODE START (stock code replaced) - if ( - !VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref curPosition, ref netManager.m_segments.m_buffer[curPosition.m_segment], curTargetNodeId, curLaneId, ref nextPosition, nextSourceNodeId, ref netManager.m_nodes.m_buffer[nextSourceNodeId], nextLaneId, ref nextNextPosition, nextTargetNodeId) - ) { // NON-STOCK CODE - maxSpeed = 0; - return; - } else { - ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); - } - // NON-STOCK CODE END - } - - if (prevSegmentInfo.m_lanes != null && prevSegmentInfo.m_lanes.Length > nextPosition.m_lane) { - // NON-STOCK CODE START - float laneSpeedLimit = 1f; - - if (!Options.customSpeedLimitsEnabled) { - laneSpeedLimit = prevSegmentInfo.m_lanes[nextPosition.m_lane].m_speedLimit; - } else { - laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(nextPosition.m_segment, nextPosition.m_lane, nextLaneId, prevSegmentInfo.m_lanes[nextPosition.m_lane]); - } - - // NON-STOCK CODE END - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[nextLaneId].m_curve); - } else { - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); - } - - // NON-STOCK CODE START (stock code replaced) - maxSpeed = Constants.ManagerFactory.VehicleBehaviorManager.CalcMaxSpeed(vehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, nextPosition, ref netManager.m_segments.m_buffer[nextPosition.m_segment], pos, maxSpeed, false); - // NON-STOCK CODE END - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position position, uint laneId, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) { - var netManager = Singleton.instance; - var segmentInfo = netManager.m_segments.m_buffer[position.m_segment].Info; - netManager.m_lanes.m_buffer[laneId].CalculatePositionAndDirection(offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); - - if (segmentInfo.m_lanes != null && segmentInfo.m_lanes.Length > position.m_lane) { - // NON-STOCK CODE START - float laneSpeedLimit = 1f; - if (!Options.customSpeedLimitsEnabled) { - laneSpeedLimit = segmentInfo.m_lanes[position.m_lane].m_speedLimit; - } else { - laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneId, segmentInfo.m_lanes[position.m_lane]); - } - // NON-STOCK CODE END - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[laneId].m_curve); - } else { - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); - } - - // NON-STOCK CODE START - maxSpeed = VehicleBehaviorManager.Instance.CalcMaxSpeed(vehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, position, ref netManager.m_segments.m_buffer[position.m_segment], pos, maxSpeed, false); - // NON-STOCK CODE END - } - - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); + Vector3 lastFrameVehiclePos = lastFrameData.m_position; + float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; + var prevSegmentInfo = netManager.m_segments.m_buffer[nextPosition.m_segment].Info; + netManager.m_lanes.m_buffer[nextLaneId].CalculatePositionAndDirection(nextOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); + + float braking = this.m_info.m_braking; + if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != (Vehicle.Flags)0) { + braking *= 2f; + } + + // car position on the Bezier curve of the lane + var refVehiclePosOnBezier = netManager.m_lanes.m_buffer[curLaneId].CalculatePosition(curOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + //ushort currentSegmentId = netManager.m_lanes.m_buffer[prevLaneID].m_segment; + + // this seems to be like the required braking force in order to stop the vehicle within its half length. + var crazyValue = 0.5f * sqrVelocity / braking + m_info.m_generatedInfo.m_size.z * 0.5f; + bool withinBrakingDistance = Vector3.Distance(lastFrameVehiclePos, refVehiclePosOnBezier) >= crazyValue - 1f; + + if ( + nextSourceNodeId == curTargetNodeId && + withinBrakingDistance + ) { + // NON-STOCK CODE START (stock code replaced) + if ( + !VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref curPosition, ref netManager.m_segments.m_buffer[curPosition.m_segment], curTargetNodeId, curLaneId, ref nextPosition, nextSourceNodeId, ref netManager.m_nodes.m_buffer[nextSourceNodeId], nextLaneId, ref nextNextPosition, nextTargetNodeId) + ) { // NON-STOCK CODE + maxSpeed = 0; + return; + } else { + ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); + } + // NON-STOCK CODE END + } + + if (prevSegmentInfo.m_lanes != null && prevSegmentInfo.m_lanes.Length > nextPosition.m_lane) { + // NON-STOCK CODE START + float laneSpeedLimit = 1f; + + if (!Options.customSpeedLimitsEnabled) { + laneSpeedLimit = prevSegmentInfo.m_lanes[nextPosition.m_lane].m_speedLimit; + } else { + laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(nextPosition.m_segment, nextPosition.m_lane, nextLaneId, prevSegmentInfo.m_lanes[nextPosition.m_lane]); + } + + // NON-STOCK CODE END + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[nextLaneId].m_curve); + } else { + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); + } + + // NON-STOCK CODE START (stock code replaced) + maxSpeed = Constants.ManagerFactory.VehicleBehaviorManager.CalcMaxSpeed(vehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, nextPosition, ref netManager.m_segments.m_buffer[nextPosition.m_segment], pos, maxSpeed, false); + // NON-STOCK CODE END + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position position, uint laneId, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) { + var netManager = Singleton.instance; + var segmentInfo = netManager.m_segments.m_buffer[position.m_segment].Info; + netManager.m_lanes.m_buffer[laneId].CalculatePositionAndDirection(offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); + + if (segmentInfo.m_lanes != null && segmentInfo.m_lanes.Length > position.m_lane) { + // NON-STOCK CODE START + float laneSpeedLimit = 1f; + if (!Options.customSpeedLimitsEnabled) { + laneSpeedLimit = segmentInfo.m_lanes[position.m_lane].m_speedLimit; + } else { + laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneId, segmentInfo.m_lanes[position.m_lane]); + } + // NON-STOCK CODE END + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[laneId].m_curve); + } else { + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); + } + + // NON-STOCK CODE START + maxSpeed = VehicleBehaviorManager.Instance.CalcMaxSpeed(vehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, position, ref netManager.m_segments.m_buffer[position.m_segment], pos, maxSpeed, false); + // NON-STOCK CODE END + } + + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { #if DEBUG - bool vehDebug = GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && vehDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && vehDebug; + bool vehDebug = GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && vehDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && vehDebug; - if (debug) - Log.Warning($"CustomCarAI.CustomStartPathFind({vehicleID}): called for vehicle {vehicleID}, startPos={startPos}, endPos={endPos}, startBothWays={startBothWays}, endBothWays={endBothWays}, undergroundTarget={undergroundTarget}"); + if (debug) + Log.Warning($"CustomCarAI.CustomStartPathFind({vehicleID}): called for vehicle {vehicleID}, startPos={startPos}, endPos={endPos}, startBothWays={startBothWays}, endBothWays={endBothWays}, undergroundTarget={undergroundTarget}"); #endif - ExtVehicleType vehicleType = ExtVehicleType.None; + ExtVehicleType vehicleType = ExtVehicleType.None; #if BENCHMARK using (var bm = new Benchmark(null, "OnStartPathFind")) { #endif - vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); - if (vehicleType == ExtVehicleType.None) { + vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); + if (vehicleType == ExtVehicleType.None) { #if DEBUG - Log.Warning($"CustomCarAI.CustomStartPathFind({vehicleID}): Vehicle {vehicleID} does not have a valid vehicle type!"); + Log.Warning($"CustomCarAI.CustomStartPathFind({vehicleID}): Vehicle {vehicleID} does not have a valid vehicle type!"); #endif - vehicleType = ExtVehicleType.RoadVehicle; - } + vehicleType = ExtVehicleType.RoadVehicle; + } #if BENCHMARK } #endif - VehicleInfo info = this.m_info; - bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startDistSqrA; - float startDistSqrB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endDistSqrA; - float endDistSqrB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && - CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; - - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = vehicleType; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + VehicleInfo info = this.m_info; + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startDistSqrA; + float startDistSqrB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endDistSqrA; + float endDistSqrB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && + CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; + + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = vehicleType; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { #if DEBUG - if (debug) - Log._Debug($"CustomCarAI.CustomStartPathFind({vehicleID}): Path-finding starts for vehicle {vehicleID}, path={path}, extVehicleType={vehicleType}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, info.m_vehicleType={info.m_vehicleType}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}"); + if (debug) + Log._Debug($"CustomCarAI.CustomStartPathFind({vehicleID}): Path-finding starts for vehicle {vehicleID}, path={path}, extVehicleType={vehicleType}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, info.m_vehicleType={info.m_vehicleType}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}"); #endif - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } - - [RedirectMethod] - public static ushort CustomCheckOtherVehicle(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ref float maxSpeed, ref bool blocked, ref Vector3 collisionPush, float maxBraking, ushort otherID, ref Vehicle otherData, Vector3 min, Vector3 max, int lodPhysics) { - if (otherID == vehicleID || vehicleData.m_leadingVehicle == otherID || vehicleData.m_trailingVehicle == otherID) { - return otherData.m_nextGridVehicle; - } - - VehicleInfo info = otherData.Info; - if (info.m_vehicleType == VehicleInfo.VehicleType.Bicycle) { - return otherData.m_nextGridVehicle; - } - - if (((vehicleData.m_flags | otherData.m_flags) & Vehicle.Flags.Transition) == (Vehicle.Flags)0 && (vehicleData.m_flags & Vehicle.Flags.Underground) != (otherData.m_flags & Vehicle.Flags.Underground)) { - return otherData.m_nextGridVehicle; - } + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } + + [RedirectMethod] + public static ushort CustomCheckOtherVehicle(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ref float maxSpeed, ref bool blocked, ref Vector3 collisionPush, float maxBraking, ushort otherID, ref Vehicle otherData, Vector3 min, Vector3 max, int lodPhysics) { + if (otherID == vehicleID || vehicleData.m_leadingVehicle == otherID || vehicleData.m_trailingVehicle == otherID) { + return otherData.m_nextGridVehicle; + } + + VehicleInfo info = otherData.Info; + if (info.m_vehicleType == VehicleInfo.VehicleType.Bicycle) { + return otherData.m_nextGridVehicle; + } + + if (((vehicleData.m_flags | otherData.m_flags) & Vehicle.Flags.Transition) == (Vehicle.Flags)0 && (vehicleData.m_flags & Vehicle.Flags.Underground) != (otherData.m_flags & Vehicle.Flags.Underground)) { + return otherData.m_nextGridVehicle; + } #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[24] && - (GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.None || GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.RoadVehicle) && - (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID); - - if (debug) { - Log._Debug($"CustomCarAI.CustomCheckOtherVehicle({vehicleID}, {otherID}) called."); - } + bool debug = GlobalConfig.Instance.Debug.Switches[24] && + (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.RoadVehicle) + && (GlobalConfig.Instance.Debug.VehicleId == 0 + || GlobalConfig.Instance.Debug.VehicleId == vehicleID); + + if (debug) { + Log._Debug($"CustomCarAI.CustomCheckOtherVehicle({vehicleID}, {otherID}) called."); + } #endif - Vector3 otherSegMin; - Vector3 otherSegMax; - if (lodPhysics >= 2) { - otherSegMin = otherData.m_segment.Min(); - otherSegMax = otherData.m_segment.Max(); - } else { - otherSegMin = Vector3.Min(otherData.m_segment.Min(), otherData.m_targetPos3); - otherSegMax = Vector3.Max(otherData.m_segment.Max(), otherData.m_targetPos3); - } - - if ( - min.x >= otherSegMax.x + 2f && - min.y >= otherSegMax.y + 2f && - min.z >= otherSegMax.z + 2f && - otherSegMin.x >= max.x + 2f && - otherSegMin.y >= max.y + 2f && - otherSegMin.z >= max.z + 2f - ) { - return otherData.m_nextGridVehicle; - } - - Vehicle.Frame otherFrameData = otherData.GetLastFrameData(); - if (lodPhysics < 2) { - float u = default(float); - float v = default(float); - float segSqrDist = vehicleData.m_segment.DistanceSqr(otherData.m_segment, out u, out v); - if (segSqrDist < 4f) { - Vector3 vehPos = vehicleData.m_segment.Position(0.5f); - Vector3 otherPos = otherData.m_segment.Position(0.5f); - Vector3 vehBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; - if (Vector3.Dot(vehBounds, vehPos - otherPos) < 0f) { - collisionPush -= vehBounds.normalized * (0.1f - segSqrDist * 0.025f); - } else { - collisionPush += vehBounds.normalized * (0.1f - segSqrDist * 0.025f); - } - blocked = true; - } - } - - float vehVelocity = frameData.m_velocity.magnitude + 0.01f; - float otherVehVelocity = otherFrameData.m_velocity.magnitude; - float otherBreakingDist = otherVehVelocity * (0.5f + 0.5f * otherVehVelocity / info.m_braking) + Mathf.Min(1f, otherVehVelocity); - otherVehVelocity += 0.01f; - float prevLength = 0f; - Vector3 prevTargetPos = vehicleData.m_segment.b; - Vector3 prevBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; - int startI = (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; - for (int i = startI; i < 4; i++) { - Vector3 targetPos = vehicleData.GetTargetPos(i); - Vector3 targetPosDiff = targetPos - prevTargetPos; - if (Vector3.Dot(prevBounds, targetPosDiff) > 0f) { - float targetPosDiffLen = targetPosDiff.magnitude; - Segment3 curSegment = new Segment3(prevTargetPos, targetPos); - min = curSegment.Min(); - max = curSegment.Max(); - curSegment.a.y *= 0.5f; - curSegment.b.y *= 0.5f; - - if ( - targetPosDiffLen > 0.01f && - min.x < otherSegMax.x + 2f && - min.y < otherSegMax.y + 2f && - min.z < otherSegMax.z + 2f && - otherSegMin.x < max.x + 2f && - otherSegMin.y < max.y + 2f && - otherSegMin.z < max.z + 2f - ) { - Vector3 otherVehFrontPos = otherData.m_segment.a; - otherVehFrontPos.y *= 0.5f; - float u = default(float); - if (curSegment.DistanceSqr(otherVehFrontPos, out u) < 4f) { - float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; - float uDist = prevLength + targetPosDiffLen * u; - if (uDist >= 0.01f) { - uDist -= otherCosAngleToTargetPosDiff + 3f; - float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); - if (speed < 0.01f) { - blocked = true; - } - - Vector3 normOtherDir = Vector3.Normalize((Vector3)otherData.m_targetPos0 - otherData.m_segment.a); - float blockFactor = 1.2f - 1f / ((float)(int)vehicleData.m_blockCounter * 0.02f + 0.5f); - if (Vector3.Dot(targetPosDiff, normOtherDir) > blockFactor * targetPosDiffLen) { - maxSpeed = Mathf.Min(maxSpeed, speed); - } - } - break; - } - - if (lodPhysics < 2) { - float totalDist = 0f; - float otherBreakDist = otherBreakingDist; - Vector3 otherFrontPos = otherData.m_segment.b; - Vector3 otherBounds = otherData.m_segment.b - otherData.m_segment.a; - int startOtherTargetPosIndex = (info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; - bool exitTargetPosLoop = false; - int otherTargetPosIndex = startOtherTargetPosIndex; - while (otherTargetPosIndex < 4 && otherBreakDist > 0.1f) { - Vector3 otherTargetPos; - if (otherData.m_leadingVehicle == 0) { - otherTargetPos = otherData.GetTargetPos(otherTargetPosIndex); - } else { - if (otherTargetPosIndex != startOtherTargetPosIndex) { - break; - } - otherTargetPos = Singleton.instance.m_vehicles.m_buffer[otherData.m_leadingVehicle].m_segment.b; - } - - Vector3 minBreakPos = Vector3.ClampMagnitude(otherTargetPos - otherFrontPos, otherBreakDist); - if (Vector3.Dot(otherBounds, minBreakPos) > 0f) { - otherTargetPos = otherFrontPos + minBreakPos; - float breakPosDist = minBreakPos.magnitude; - otherBreakDist -= breakPosDist; - Segment3 otherVehNextSegment = new Segment3(otherFrontPos, otherTargetPos); - otherVehNextSegment.a.y *= 0.5f; - otherVehNextSegment.b.y *= 0.5f; - if (breakPosDist > 0.01f) { - float otherVehNextSegU = default(float); - float otherVehNextSegV = default(float); - float otherVehNextSegmentDistToCurSegment = (otherID >= vehicleID) - ? curSegment.DistanceSqr(otherVehNextSegment, out otherVehNextSegU, out otherVehNextSegV) - : otherVehNextSegment.DistanceSqr(curSegment, out otherVehNextSegV, out otherVehNextSegU); - if (otherVehNextSegmentDistToCurSegment < 4f) { - float uDist = prevLength + targetPosDiffLen * otherVehNextSegU; - float vDist = totalDist + breakPosDist * otherVehNextSegV + 0.1f; - if (uDist >= 0.01f && uDist * otherVehVelocity > vDist * vehVelocity) { - float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; - if (uDist >= 0.01f) { - uDist -= otherCosAngleToTargetPosDiff + 1f + otherData.Info.m_generatedInfo.m_size.z; - float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); - if (speed < 0.01f) { - blocked = true; - } - maxSpeed = Mathf.Min(maxSpeed, speed); - } - } - exitTargetPosLoop = true; - break; - } - } - otherBounds = minBreakPos; - totalDist += breakPosDist; - otherFrontPos = otherTargetPos; - } - otherTargetPosIndex++; - } - if (exitTargetPosLoop) { - break; - } - } - } - prevBounds = targetPosDiff; - prevLength += targetPosDiffLen; - prevTargetPos = targetPos; - } - } - - return otherData.m_nextGridVehicle; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private static bool CheckOverlap(Segment3 segment, ushort ignoreVehicle, float maxVelocity) { - Log.Error("CustomCarAI.CheckOverlap called"); - return false; - } - - /*[MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private static ushort CheckOtherVehicle(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ref float maxSpeed, ref bool blocked, ref Vector3 collisionPush, float maxBraking, ushort otherID, ref Vehicle otherData, Vector3 min, Vector3 max, int lodPhysics) { - Log.Error("CustomCarAI.CheckOtherVehicle called"); - return 0; - }*/ - - [MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private static ushort CheckCitizen(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, float lastLen, float nextLen, ref float maxSpeed, ref bool blocked, float maxBraking, ushort otherID, ref CitizenInstance otherData, Vector3 min, Vector3 max) { - Log.Error("CustomCarAI.CheckCitizen called"); - return 0; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private static float CalculateMaxSpeed(float targetDistance, float targetSpeed, float maxBraking) { - Log.Error("CustomCarAI.CalculateMaxSpeed called"); - return 0f; - } - } + Vector3 otherSegMin; + Vector3 otherSegMax; + if (lodPhysics >= 2) { + otherSegMin = otherData.m_segment.Min(); + otherSegMax = otherData.m_segment.Max(); + } else { + otherSegMin = Vector3.Min(otherData.m_segment.Min(), otherData.m_targetPos3); + otherSegMax = Vector3.Max(otherData.m_segment.Max(), otherData.m_targetPos3); + } + + if ( + min.x >= otherSegMax.x + 2f && + min.y >= otherSegMax.y + 2f && + min.z >= otherSegMax.z + 2f && + otherSegMin.x >= max.x + 2f && + otherSegMin.y >= max.y + 2f && + otherSegMin.z >= max.z + 2f + ) { + return otherData.m_nextGridVehicle; + } + + Vehicle.Frame otherFrameData = otherData.GetLastFrameData(); + if (lodPhysics < 2) { + float u = default(float); + float v = default(float); + float segSqrDist = vehicleData.m_segment.DistanceSqr(otherData.m_segment, out u, out v); + if (segSqrDist < 4f) { + Vector3 vehPos = vehicleData.m_segment.Position(0.5f); + Vector3 otherPos = otherData.m_segment.Position(0.5f); + Vector3 vehBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; + if (Vector3.Dot(vehBounds, vehPos - otherPos) < 0f) { + collisionPush -= vehBounds.normalized * (0.1f - segSqrDist * 0.025f); + } else { + collisionPush += vehBounds.normalized * (0.1f - segSqrDist * 0.025f); + } + blocked = true; + } + } + + float vehVelocity = frameData.m_velocity.magnitude + 0.01f; + float otherVehVelocity = otherFrameData.m_velocity.magnitude; + float otherBreakingDist = otherVehVelocity * (0.5f + 0.5f * otherVehVelocity / info.m_braking) + Mathf.Min(1f, otherVehVelocity); + otherVehVelocity += 0.01f; + float prevLength = 0f; + Vector3 prevTargetPos = vehicleData.m_segment.b; + Vector3 prevBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; + int startI = (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; + for (int i = startI; i < 4; i++) { + Vector3 targetPos = vehicleData.GetTargetPos(i); + Vector3 targetPosDiff = targetPos - prevTargetPos; + if (Vector3.Dot(prevBounds, targetPosDiff) > 0f) { + float targetPosDiffLen = targetPosDiff.magnitude; + Segment3 curSegment = new Segment3(prevTargetPos, targetPos); + min = curSegment.Min(); + max = curSegment.Max(); + curSegment.a.y *= 0.5f; + curSegment.b.y *= 0.5f; + + if ( + targetPosDiffLen > 0.01f && + min.x < otherSegMax.x + 2f && + min.y < otherSegMax.y + 2f && + min.z < otherSegMax.z + 2f && + otherSegMin.x < max.x + 2f && + otherSegMin.y < max.y + 2f && + otherSegMin.z < max.z + 2f + ) { + Vector3 otherVehFrontPos = otherData.m_segment.a; + otherVehFrontPos.y *= 0.5f; + float u = default(float); + if (curSegment.DistanceSqr(otherVehFrontPos, out u) < 4f) { + float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; + float uDist = prevLength + targetPosDiffLen * u; + if (uDist >= 0.01f) { + uDist -= otherCosAngleToTargetPosDiff + 3f; + float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); + if (speed < 0.01f) { + blocked = true; + } + + Vector3 normOtherDir = Vector3.Normalize((Vector3)otherData.m_targetPos0 - otherData.m_segment.a); + float blockFactor = 1.2f - 1f / ((float)(int)vehicleData.m_blockCounter * 0.02f + 0.5f); + if (Vector3.Dot(targetPosDiff, normOtherDir) > blockFactor * targetPosDiffLen) { + maxSpeed = Mathf.Min(maxSpeed, speed); + } + } + break; + } + + if (lodPhysics < 2) { + float totalDist = 0f; + float otherBreakDist = otherBreakingDist; + Vector3 otherFrontPos = otherData.m_segment.b; + Vector3 otherBounds = otherData.m_segment.b - otherData.m_segment.a; + int startOtherTargetPosIndex = (info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; + bool exitTargetPosLoop = false; + int otherTargetPosIndex = startOtherTargetPosIndex; + while (otherTargetPosIndex < 4 && otherBreakDist > 0.1f) { + Vector3 otherTargetPos; + if (otherData.m_leadingVehicle == 0) { + otherTargetPos = otherData.GetTargetPos(otherTargetPosIndex); + } else { + if (otherTargetPosIndex != startOtherTargetPosIndex) { + break; + } + otherTargetPos = Singleton.instance.m_vehicles.m_buffer[otherData.m_leadingVehicle].m_segment.b; + } + + Vector3 minBreakPos = Vector3.ClampMagnitude(otherTargetPos - otherFrontPos, otherBreakDist); + if (Vector3.Dot(otherBounds, minBreakPos) > 0f) { + otherTargetPos = otherFrontPos + minBreakPos; + float breakPosDist = minBreakPos.magnitude; + otherBreakDist -= breakPosDist; + Segment3 otherVehNextSegment = new Segment3(otherFrontPos, otherTargetPos); + otherVehNextSegment.a.y *= 0.5f; + otherVehNextSegment.b.y *= 0.5f; + if (breakPosDist > 0.01f) { + float otherVehNextSegU = default(float); + float otherVehNextSegV = default(float); + float otherVehNextSegmentDistToCurSegment = (otherID >= vehicleID) + ? curSegment.DistanceSqr(otherVehNextSegment, out otherVehNextSegU, out otherVehNextSegV) + : otherVehNextSegment.DistanceSqr(curSegment, out otherVehNextSegV, out otherVehNextSegU); + if (otherVehNextSegmentDistToCurSegment < 4f) { + float uDist = prevLength + targetPosDiffLen * otherVehNextSegU; + float vDist = totalDist + breakPosDist * otherVehNextSegV + 0.1f; + if (uDist >= 0.01f && uDist * otherVehVelocity > vDist * vehVelocity) { + float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; + if (uDist >= 0.01f) { + uDist -= otherCosAngleToTargetPosDiff + 1f + otherData.Info.m_generatedInfo.m_size.z; + float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); + if (speed < 0.01f) { + blocked = true; + } + maxSpeed = Mathf.Min(maxSpeed, speed); + } + } + exitTargetPosLoop = true; + break; + } + } + otherBounds = minBreakPos; + totalDist += breakPosDist; + otherFrontPos = otherTargetPos; + } + otherTargetPosIndex++; + } + if (exitTargetPosLoop) { + break; + } + } + } + prevBounds = targetPosDiff; + prevLength += targetPosDiffLen; + prevTargetPos = targetPos; + } + } + + return otherData.m_nextGridVehicle; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private static bool CheckOverlap(Segment3 segment, ushort ignoreVehicle, float maxVelocity) { + Log.Error("CustomCarAI.CheckOverlap called"); + return false; + } + + /*[MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private static ushort CheckOtherVehicle(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ref float maxSpeed, ref bool blocked, ref Vector3 collisionPush, float maxBraking, ushort otherID, ref Vehicle otherData, Vector3 min, Vector3 max, int lodPhysics) { + Log.Error("CustomCarAI.CheckOtherVehicle called"); + return 0; + }*/ + + [MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private static ushort CheckCitizen(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, float lastLen, float nextLen, ref float maxSpeed, ref bool blocked, float maxBraking, ushort otherID, ref CitizenInstance otherData, Vector3 min, Vector3 max) { + Log.Error("CustomCarAI.CheckCitizen called"); + return 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private static float CalculateMaxSpeed(float targetDistance, float targetSpeed, float maxBraking) { + Log.Error("CustomCarAI.CalculateMaxSpeed called"); + return 0f; + } + } } \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs b/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs index 0e5fa8832..7f8864e07 100644 --- a/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs +++ b/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs @@ -1,149 +1,143 @@ -using System; -using ColossalFramework; -using UnityEngine; -using TrafficManager.State; -using TrafficManager.Geometry; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Traffic; -using TrafficManager.Manager; -using CSUtil.Commons; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic.Data; -using CSUtil.Commons.Benchmark; -using static TrafficManager.Custom.PathFinding.CustomPathManager; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; -using System.Runtime.CompilerServices; - namespace TrafficManager.Custom.AI { - [TargetType(typeof(CargoTruckAI))] - public class CustomCargoTruckAI : CarAI { - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { - if ((vehicleData.m_flags & Vehicle.Flags.Congestion) != 0 && VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { - Singleton.instance.ReleaseVehicle(vehicleId); - return; - } + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; + + [TargetType(typeof(CargoTruckAI))] + public class CustomCargoTruckAI : CarAI { + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { + if ((vehicleData.m_flags & Vehicle.Flags.Congestion) != 0 && VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { + Singleton.instance.ReleaseVehicle(vehicleId); + return; + } - if ((vehicleData.m_flags & Vehicle.Flags.WaitingTarget) != 0 && (vehicleData.m_waitCounter += 1) > 20) { - RemoveOffers(vehicleId, ref vehicleData); - vehicleData.m_flags &= ~Vehicle.Flags.WaitingTarget; - vehicleData.m_flags |= Vehicle.Flags.GoingBack; - vehicleData.m_waitCounter = 0; - if (!StartPathFind(vehicleId, ref vehicleData)) { - vehicleData.Unspawn(vehicleId); - } - } + if ((vehicleData.m_flags & Vehicle.Flags.WaitingTarget) != 0 && (vehicleData.m_waitCounter += 1) > 20) { + RemoveOffers(vehicleId, ref vehicleData); + vehicleData.m_flags &= ~Vehicle.Flags.WaitingTarget; + vehicleData.m_flags |= Vehicle.Flags.GoingBack; + vehicleData.m_waitCounter = 0; + if (!StartPathFind(vehicleId, ref vehicleData)) { + vehicleData.Unspawn(vehicleId); + } + } - base.SimulationStep(vehicleId, ref vehicleData, physicsLodRefPos); - } + base.SimulationStep(vehicleId, ref vehicleData, physicsLodRefPos); + } - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { #if DEBUG - //Log._Debug($"CustomCargoTruckAI.CustomStartPathFind called for vehicle {vehicleID}"); + //Log._Debug($"CustomCargoTruckAI.CustomStartPathFind called for vehicle {vehicleID}"); #endif - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); - if (vehicleType == ExtVehicleType.None) { + ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); + if (vehicleType == ExtVehicleType.None) { #if DEBUG - Log.Warning($"CustomCargoTruck.CustomStartPathFind: Vehicle {vehicleID} does not have a valid vehicle type!"); + Log.Warning($"CustomCargoTruck.CustomStartPathFind: Vehicle {vehicleID} does not have a valid vehicle type!"); #endif - } + } - if ((vehicleData.m_flags & (Vehicle.Flags.TransferToSource | Vehicle.Flags.GoingBack)) != 0) { - return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); - } + if ((vehicleData.m_flags & (Vehicle.Flags.TransferToSource | Vehicle.Flags.GoingBack)) != 0) { + return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); + } - bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startDistSqrA; - float startDistSqrB; - bool startPosFound = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB); - PathUnit.Position startAltPosA; - PathUnit.Position startAltPosB; - float startAltDistSqrA; - float startAltDistSqrB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, allowUnderground, false, 32f, out startAltPosA, out startAltPosB, out startAltDistSqrA, out startAltDistSqrB)) { - if (!startPosFound || (startAltDistSqrA < startDistSqrA && (Mathf.Abs(endPos.x) > 8000f || Mathf.Abs(endPos.z) > 8000f))) { - startPosA = startAltPosA; - startPosB = startAltPosB; - startDistSqrA = startAltDistSqrA; - startDistSqrB = startAltDistSqrB; - } - startPosFound = true; - } - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endDistSqrA; - float endDistSqrB; - bool endPosFound = CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB); - PathUnit.Position endAltPosA; - PathUnit.Position endAltPosB; - float endAltDistSqrA; - float endAltDistSqrB; - if (CustomPathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, undergroundTarget, false, 32f, out endAltPosA, out endAltPosB, out endAltDistSqrA, out endAltDistSqrB)) { - if (!endPosFound || (endAltDistSqrA < endDistSqrA && (Mathf.Abs(endPos.x) > 8000f || Mathf.Abs(endPos.z) > 8000f))) { - endPosA = endAltPosA; - endPosB = endAltPosB; - endDistSqrA = endAltDistSqrA; - endDistSqrB = endAltDistSqrB; - } - endPosFound = true; - } - if (startPosFound && endPosFound) { - CustomPathManager pathMan = CustomPathManager._instance; - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.CargoVehicle; - VehicleInfo.VehicleType vehicleTypes = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane; - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = ExtVehicleType.CargoVehicle; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = laneTypes; - args.vehicleTypes = vehicleTypes; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startDistSqrA; + float startDistSqrB; + bool startPosFound = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB); + PathUnit.Position startAltPosA; + PathUnit.Position startAltPosB; + float startAltDistSqrA; + float startAltDistSqrB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, allowUnderground, false, 32f, out startAltPosA, out startAltPosB, out startAltDistSqrA, out startAltDistSqrB)) { + if (!startPosFound || (startAltDistSqrA < startDistSqrA && (Mathf.Abs(endPos.x) > 8000f || Mathf.Abs(endPos.z) > 8000f))) { + startPosA = startAltPosA; + startPosB = startAltPosB; + startDistSqrA = startAltDistSqrA; + startDistSqrB = startAltDistSqrB; + } + startPosFound = true; + } + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endDistSqrA; + float endDistSqrB; + bool endPosFound = CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB); + PathUnit.Position endAltPosA; + PathUnit.Position endAltPosB; + float endAltDistSqrA; + float endAltDistSqrB; + if (CustomPathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, undergroundTarget, false, 32f, out endAltPosA, out endAltPosB, out endAltDistSqrA, out endAltDistSqrB)) { + if (!endPosFound || (endAltDistSqrA < endDistSqrA && (Mathf.Abs(endPos.x) > 8000f || Mathf.Abs(endPos.z) > 8000f))) { + endPosA = endAltPosA; + endPosB = endAltPosB; + endDistSqrA = endAltDistSqrA; + endDistSqrB = endAltDistSqrB; + } + endPosFound = true; + } + if (startPosFound && endPosFound) { + CustomPathManager pathMan = CustomPathManager._instance; + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.CargoVehicle; + VehicleInfo.VehicleType vehicleTypes = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane; + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = ExtVehicleType.CargoVehicle; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = laneTypes; + args.vehicleTypes = vehicleTypes; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - if (pathMan.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - pathMan.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } + if (pathMan.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + pathMan.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } - [MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private void RemoveOffers(ushort vehicleId, ref Vehicle data) { - Log.Error("CustomCargoTruckAI.RemoveOffers called"); - } - } -} + [MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private void RemoveOffers(ushort vehicleId, ref Vehicle data) { + Log.Error("CustomCargoTruckAI.RemoveOffers called"); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomFireTruckAI.cs b/TLM/TLM/Custom/AI/CustomFireTruckAI.cs index c32d788f5..c3b444775 100644 --- a/TLM/TLM/Custom/AI/CustomFireTruckAI.cs +++ b/TLM/TLM/Custom/AI/CustomFireTruckAI.cs @@ -1,91 +1,84 @@ -using ColossalFramework; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(FireTruckAI))] - public class CustomFireTruckAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + [TargetType(typeof(FireTruckAI))] + public class CustomFireTruckAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { #if DEBUG - //Log._Debug($"CustomFireTruckAI.CustomStartPathFind called for vehicle {vehicleID}"); + //Log._Debug($"CustomFireTruckAI.CustomStartPathFind called for vehicle {vehicleID}"); #endif - ExtVehicleType vehicleType = ExtVehicleType.None; + ExtVehicleType vehicleType = ExtVehicleType.None; #if BENCHMARK using (var bm = new Benchmark(null, "OnStartPathFind")) { #endif - vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); + vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); #if BENCHMARK } #endif - VehicleInfo info = this.m_info; - bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startDistSqrA; - float startDistSqrB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endDistSqrA; - float endDistSqrB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && - CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = vehicleType; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + VehicleInfo info = this.m_info; + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startDistSqrA; + float startDistSqrB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endDistSqrA; + float endDistSqrB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && + CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = vehicleType; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } else { - PathfindFailure(vehicleID, ref vehicleData); - } - return false; - } - } -} + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } else { + PathfindFailure(vehicleID, ref vehicleData); + } + return false; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomHumanAI.cs b/TLM/TLM/Custom/AI/CustomHumanAI.cs index 5b318a197..833a36b2f 100644 --- a/TLM/TLM/Custom/AI/CustomHumanAI.cs +++ b/TLM/TLM/Custom/AI/CustomHumanAI.cs @@ -20,6 +20,9 @@ using TrafficManager.RedirectionFramework.Attributes; namespace TrafficManager.Custom.AI { + using API.Traffic.Enums; + using API.TrafficLight; + [TargetType(typeof(HumanAI))] public class CustomHumanAI : CitizenAI { [RedirectMethod] diff --git a/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs b/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs index 0abcc1fd5..dd6a13dbd 100644 --- a/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs +++ b/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs @@ -1,82 +1,75 @@ -using ColossalFramework; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(PoliceCarAI))] - public class CustomPoliceCarAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); + [TargetType(typeof(PoliceCarAI))] + public class CustomPoliceCarAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); - VehicleInfo info = this.m_info; - bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startDistSqrA; - float startDistSqrB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endDistSqrA; - float endDistSqrB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && - CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = vehicleType; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + VehicleInfo info = this.m_info; + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startDistSqrA; + float startDistSqrB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endDistSqrA; + float endDistSqrB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) && + CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) { + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = vehicleType; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } - } -} + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomPostVanAI.cs b/TLM/TLM/Custom/AI/CustomPostVanAI.cs index 89f76c79f..5f7c073e6 100644 --- a/TLM/TLM/Custom/AI/CustomPostVanAI.cs +++ b/TLM/TLM/Custom/AI/CustomPostVanAI.cs @@ -1,119 +1,114 @@ -using ColossalFramework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.RedirectionFramework.Attributes; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(PostVanAI))] - public class CustomPostVanAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - if (vehicleData.m_transferType == (byte)TransferManager.TransferReason.Mail) { - return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); - } + [TargetType(typeof(PostVanAI))] + public class CustomPostVanAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + if (vehicleData.m_transferType == (byte)TransferManager.TransferReason.Mail) { + return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); + } - if ((vehicleData.m_flags & (Vehicle.Flags.TransferToSource | Vehicle.Flags.GoingBack)) != 0) { - return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); - } + if ((vehicleData.m_flags & (Vehicle.Flags.TransferToSource | Vehicle.Flags.GoingBack)) != 0) { + return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); + } - bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != (Vehicle.Flags)0; - PathUnit.Position startPosA = default(PathUnit.Position); - PathUnit.Position startPosB = default(PathUnit.Position); - float startDistSqrA = default(float); - float startDistSqrB = default(float); + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != (Vehicle.Flags)0; + PathUnit.Position startPosA = default(PathUnit.Position); + PathUnit.Position startPosB = default(PathUnit.Position); + float startDistSqrA = default(float); + float startDistSqrB = default(float); - // try to find road start position - bool startPosFound = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB); + // try to find road start position + bool startPosFound = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB); - // try to find other start position (plane, train, ship) - PathUnit.Position altStartPosA = default(PathUnit.Position); - PathUnit.Position altStartPosB = default(PathUnit.Position); - float altStartDistSqrA = default(float); - float altStartDistSqrB = default(float); - if (PathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, allowUnderground, false, 32f, out altStartPosA, out altStartPosB, out altStartDistSqrA, out altStartDistSqrB)) { - if (!startPosFound || (altStartDistSqrA < startDistSqrA && (Mathf.Abs(startPos.x) > 4800f || Mathf.Abs(startPos.z) > 4800f))) { - startPosA = altStartPosA; - startPosB = altStartPosB; - startDistSqrA = altStartDistSqrA; - startDistSqrB = altStartDistSqrB; - } - startPosFound = true; - } + // try to find other start position (plane, train, ship) + PathUnit.Position altStartPosA = default(PathUnit.Position); + PathUnit.Position altStartPosB = default(PathUnit.Position); + float altStartDistSqrA = default(float); + float altStartDistSqrB = default(float); + if (PathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, allowUnderground, false, 32f, out altStartPosA, out altStartPosB, out altStartDistSqrA, out altStartDistSqrB)) { + if (!startPosFound || (altStartDistSqrA < startDistSqrA && (Mathf.Abs(startPos.x) > 4800f || Mathf.Abs(startPos.z) > 4800f))) { + startPosA = altStartPosA; + startPosB = altStartPosB; + startDistSqrA = altStartDistSqrA; + startDistSqrB = altStartDistSqrB; + } + startPosFound = true; + } - PathUnit.Position endPosA = default(PathUnit.Position); - PathUnit.Position endPosB = default(PathUnit.Position); - float endDistSqrA = default(float); - float endDistSqrB = default(float); + PathUnit.Position endPosA = default(PathUnit.Position); + PathUnit.Position endPosB = default(PathUnit.Position); + float endDistSqrA = default(float); + float endDistSqrB = default(float); - // try to find road end position - bool endPosFound = PathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB); + // try to find road end position + bool endPosFound = PathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB); - // try to find other end position (plane, train, ship) - PathUnit.Position altEndPosA = default(PathUnit.Position); - PathUnit.Position altEndPosB = default(PathUnit.Position); - float altEndDistSqrA = default(float); - float altEndDistSqrB = default(float); - if (PathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, undergroundTarget, false, 32f, out altEndPosA, out altEndPosB, out altEndDistSqrA, out altEndDistSqrB)) { - if (!endPosFound || (altEndDistSqrA < endDistSqrA && (Mathf.Abs(endPos.x) > 4800f || Mathf.Abs(endPos.z) > 4800f))) { - endPosA = altEndPosA; - endPosB = altEndPosB; - endDistSqrA = altEndDistSqrA; - endDistSqrB = altEndDistSqrB; - } - endPosFound = true; - } + // try to find other end position (plane, train, ship) + PathUnit.Position altEndPosA = default(PathUnit.Position); + PathUnit.Position altEndPosB = default(PathUnit.Position); + float altEndDistSqrA = default(float); + float altEndDistSqrB = default(float); + if (PathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, undergroundTarget, false, 32f, out altEndPosA, out altEndPosB, out altEndDistSqrA, out altEndDistSqrB)) { + if (!endPosFound || (altEndDistSqrA < endDistSqrA && (Mathf.Abs(endPos.x) > 4800f || Mathf.Abs(endPos.z) > 4800f))) { + endPosA = altEndPosA; + endPosB = altEndPosB; + endDistSqrA = altEndDistSqrA; + endDistSqrB = altEndDistSqrB; + } + endPosFound = true; + } - if (startPosFound && endPosFound) { - CustomPathManager pathManager = CustomPathManager._instance; - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; + if (startPosFound && endPosFound) { + CustomPathManager pathManager = CustomPathManager._instance; + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = ExtVehicleType.Service; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.CargoVehicle; - args.vehicleTypes = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = ExtVehicleType.Service; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.CargoVehicle; + args.vehicleTypes = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - if (pathManager.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - if (vehicleData.m_path != 0) { - pathManager.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } - } -} + if (pathManager.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + if (vehicleData.m_path != 0) { + pathManager.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomShipAI.cs b/TLM/TLM/Custom/AI/CustomShipAI.cs index ef1b0f0f6..a75397113 100644 --- a/TLM/TLM/Custom/AI/CustomShipAI.cs +++ b/TLM/TLM/Custom/AI/CustomShipAI.cs @@ -1,87 +1,79 @@ -using ColossalFramework; -using CSUtil.Commons; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(ShipAI))] - public class CustomShipAI : ShipAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { + [TargetType(typeof(ShipAI))] + public class CustomShipAI : ShipAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { #if DEBUG - //Log._Debug($"CustomShipAI.CustomStartPathFind called for vehicle {vehicleID}"); + //Log._Debug($"CustomShipAI.CustomStartPathFind called for vehicle {vehicleID}"); #endif - /// NON-STOCK CODE START /// - ExtVehicleType vehicleType = vehicleData.Info.m_vehicleAI is PassengerShipAI ? ExtVehicleType.PassengerShip : ExtVehicleType.CargoVehicle; - /// NON-STOCK CODE END /// + /// NON-STOCK CODE START /// + ExtVehicleType vehicleType = vehicleData.Info.m_vehicleAI is PassengerShipAI ? ExtVehicleType.PassengerShip : ExtVehicleType.CargoVehicle; + /// NON-STOCK CODE END /// - VehicleInfo info = this.m_info; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startSqrDistA; - float startSqrDistB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endSqrDistA; - float endSqrDistB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, info.m_vehicleType, false, false, 64f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && - CustomPathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, info.m_vehicleType, false, false, 64f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { - if (!startBothWays || startSqrDistA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endSqrDistA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = vehicleType; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = false; - args.hasCombustionEngine = false; - args.ignoreBlocked = false; - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = false; + VehicleInfo info = this.m_info; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startSqrDistA; + float startSqrDistB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endSqrDistA; + float endSqrDistB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, info.m_vehicleType, false, false, 64f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && + CustomPathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, info.m_vehicleType, false, false, 64f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { + if (!startBothWays || startSqrDistA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endSqrDistA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = vehicleType; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = false; + args.hasCombustionEngine = false; + args.ignoreBlocked = false; + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = false; - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } - } -} + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTaxiAI.cs b/TLM/TLM/Custom/AI/CustomTaxiAI.cs index 114c1b885..838e8d6a8 100644 --- a/TLM/TLM/Custom/AI/CustomTaxiAI.cs +++ b/TLM/TLM/Custom/AI/CustomTaxiAI.cs @@ -1,89 +1,83 @@ -using ColossalFramework; -using TrafficManager.RedirectionFramework.Attributes; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TaxiAI))] - public class CustomTaxiAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - CitizenManager instance = Singleton.instance; - ushort passengerInstanceId = Constants.ManagerFactory.ExtVehicleManager.GetDriverInstanceId(vehicleID, ref vehicleData); - if (passengerInstanceId == 0 || (instance.m_instances.m_buffer[(int)passengerInstanceId].m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { - return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); - } - VehicleInfo info = this.m_info; - CitizenInfo info2 = instance.m_instances.m_buffer[(int)passengerInstanceId].Info; - NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.Pedestrian | NetInfo.LaneType.TransportVehicle; - VehicleInfo.VehicleType vehicleTypes = this.m_info.m_vehicleType; - bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0; - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startSqrDistA; - float startSqrDistB; - PathUnit.Position endPosA; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && - Constants.ManagerFactory.ExtCitizenInstanceManager.FindPathPosition(passengerInstanceId, ref instance.m_instances.m_buffer[(int)passengerInstanceId], endPos, laneTypes, vehicleTypes, undergroundTarget, out endPosA)) { - if ((instance.m_instances.m_buffer[(int)passengerInstanceId].m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { - laneTypes |= NetInfo.LaneType.PublicTransport; + [TargetType(typeof(TaxiAI))] + public class CustomTaxiAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + CitizenManager instance = Singleton.instance; + ushort passengerInstanceId = Constants.ManagerFactory.ExtVehicleManager.GetDriverInstanceId(vehicleID, ref vehicleData); + if (passengerInstanceId == 0 || (instance.m_instances.m_buffer[(int)passengerInstanceId].m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { + return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); + } + VehicleInfo info = this.m_info; + CitizenInfo info2 = instance.m_instances.m_buffer[(int)passengerInstanceId].Info; + NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.Pedestrian | NetInfo.LaneType.TransportVehicle; + VehicleInfo.VehicleType vehicleTypes = this.m_info.m_vehicleType; + bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startSqrDistA; + float startSqrDistB; + PathUnit.Position endPosA; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && + Constants.ManagerFactory.ExtCitizenInstanceManager.FindPathPosition(passengerInstanceId, ref instance.m_instances.m_buffer[(int)passengerInstanceId], endPos, laneTypes, vehicleTypes, undergroundTarget, out endPosA)) { + if ((instance.m_instances.m_buffer[(int)passengerInstanceId].m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { + laneTypes |= NetInfo.LaneType.PublicTransport; - uint citizenId = instance.m_instances.m_buffer[passengerInstanceId].m_citizen; - if (citizenId != 0u && (instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) { - laneTypes |= NetInfo.LaneType.EvacuationTransport; - } - } - if (!startBothWays || startSqrDistA < 10f) { - startPosB = default(PathUnit.Position); - } - PathUnit.Position endPosB = default(PathUnit.Position); - SimulationManager simMan = Singleton.instance; - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = ExtVehicleType.Taxi; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = simMan.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = laneTypes; - args.vehicleTypes = vehicleTypes; - args.maxLength = 20000f; - args.isHeavyVehicle = this.IsHeavyVehicle(); - args.hasCombustionEngine = this.CombustionEngine(); - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + uint citizenId = instance.m_instances.m_buffer[passengerInstanceId].m_citizen; + if (citizenId != 0u && (instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) { + laneTypes |= NetInfo.LaneType.EvacuationTransport; + } + } + if (!startBothWays || startSqrDistA < 10f) { + startPosB = default(PathUnit.Position); + } + PathUnit.Position endPosB = default(PathUnit.Position); + SimulationManager simMan = Singleton.instance; + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = ExtVehicleType.Taxi; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = simMan.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = laneTypes; + args.vehicleTypes = vehicleTypes; + args.maxLength = 20000f; + args.isHeavyVehicle = this.IsHeavyVehicle(); + args.hasCombustionEngine = this.CombustionEngine(); + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - if (CustomPathManager._instance.CustomCreatePath(out path, ref simMan.m_randomizer, args)) { - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } + if (CustomPathManager._instance.CustomCreatePath(out path, ref simMan.m_randomizer, args)) { + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } - } -} + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTrainAI.cs b/TLM/TLM/Custom/AI/CustomTrainAI.cs index 0a730751a..5ed5e0188 100644 --- a/TLM/TLM/Custom/AI/CustomTrainAI.cs +++ b/TLM/TLM/Custom/AI/CustomTrainAI.cs @@ -1,882 +1,883 @@ -using ColossalFramework; -using ColossalFramework.Math; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.State; -using TrafficManager.Geometry; -using UnityEngine; -using TrafficManager.Traffic; -using TrafficManager.Manager; -using CSUtil.Commons; -using TrafficManager.Manager.Impl; -using System.Runtime.CompilerServices; -using TrafficManager.Traffic.Data; -using CSUtil.Commons.Benchmark; -using static TrafficManager.Custom.PathFinding.CustomPathManager; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TrainAI))] - public class CustomTrainAI : TrainAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - - if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) { - byte pathFindFlags = Singleton.instance.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags; - - if ((pathFindFlags & PathUnit.FLAG_READY) != 0) { - try { - this.PathFindReady(vehicleId, ref vehicleData); - } catch (Exception e) { - Log.Warning($"TrainAI.PathFindReady({vehicleId}) for vehicle {vehicleData.Info?.m_class?.name} threw an exception: {e.ToString()}"); - vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; - Singleton.instance.ReleasePath(vehicleData.m_path); - vehicleData.m_path = 0u; - vehicleData.Unspawn(vehicleId); - return; - } - } else if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) { - vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; - Singleton.instance.ReleasePath(vehicleData.m_path); - vehicleData.m_path = 0u; - vehicleData.Unspawn(vehicleId); - return; - } - } else { - if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) { - this.TrySpawn(vehicleId, ref vehicleData); - } - } - - // NON-STOCK CODE START - extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); - - if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { - // Advanced AI traffic measurement - extVehicleMan.LogTraffic(vehicleId, ref vehicleData); - } - // NON-STOCK CODE END - - bool reversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0; - ushort connectedVehicleId; - if (reversed) { - connectedVehicleId = vehicleData.GetLastVehicle(vehicleId); - } else { - connectedVehicleId = vehicleId; - } - - VehicleManager instance = Singleton.instance; - VehicleInfo info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; - info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { - return; - } - bool newReversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0; - if (newReversed != reversed) { - reversed = newReversed; - if (reversed) { - connectedVehicleId = vehicleData.GetLastVehicle(vehicleId); - } else { - connectedVehicleId = vehicleId; - } - info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; - info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { - return; - } - newReversed = ((vehicleData.m_flags & Vehicle.Flags.Reversed) != 0); - if (newReversed != reversed) { - Singleton.instance.ReleaseVehicle(vehicleId); - return; - } - } - if (reversed) { - connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_leadingVehicle; - int num2 = 0; - while (connectedVehicleId != 0) { - info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; - info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { - return; - } - connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_leadingVehicle; - if (++num2 > 16384) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } else { - connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_trailingVehicle; - int num3 = 0; - while (connectedVehicleId != 0) { - info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; - info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { - return; - } - connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_trailingVehicle; - if (++num3 > 16384) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } - if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace | Vehicle.Flags.WaitingCargo)) == 0) { - Singleton.instance.ReleaseVehicle(vehicleId); - } else if (vehicleData.m_blockCounter == 255) { - // NON-STOCK CODE START - if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { - // NON-STOCK CODE END - Singleton.instance.ReleaseVehicle(vehicleId); - } // NON-STOCK CODE - } - } - - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ushort leaderID, ref Vehicle leaderData, int lodPhysics) { - bool reversed = (leaderData.m_flags & Vehicle.Flags.Reversed) != (Vehicle.Flags)0; - ushort frontVehicleId = (!reversed) ? vehicleData.m_leadingVehicle : vehicleData.m_trailingVehicle; - VehicleInfo vehicleInfo; - if (leaderID != vehicleID) { - vehicleInfo = leaderData.Info; - } else { - vehicleInfo = this.m_info; - } - TrainAI trainAI = vehicleInfo.m_vehicleAI as TrainAI; - if (frontVehicleId != 0) { - frameData.m_position += frameData.m_velocity * 0.4f; - } else { - frameData.m_position += frameData.m_velocity * 0.5f; - } - frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; - - Vector3 posBeforeWheelRot = frameData.m_position; - Vector3 posAfterWheelRot = frameData.m_position; - Vector3 wheelBaseRot = frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); - if (reversed) { - posBeforeWheelRot -= wheelBaseRot; - posAfterWheelRot += wheelBaseRot; - } else { - posBeforeWheelRot += wheelBaseRot; - posAfterWheelRot -= wheelBaseRot; - } - - float acceleration = this.m_info.m_acceleration; - float braking = this.m_info.m_braking; - float curSpeed = frameData.m_velocity.magnitude; - - Vector3 beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot; - float beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude; - - Quaternion curInvRot = Quaternion.Inverse(frameData.m_rotation); - Vector3 curveTangent = curInvRot * frameData.m_velocity; - - Vector3 forward = Vector3.forward; - Vector3 targetMotion = Vector3.zero; - float targetSpeed = 0f; - float motionFactor = 0.5f; - - if (frontVehicleId != 0) { - VehicleManager vehMan = Singleton.instance; - Vehicle.Frame frontVehLastFrameData = vehMan.m_vehicles.m_buffer[(int)frontVehicleId].GetLastFrameData(); - VehicleInfo frontVehInfo = vehMan.m_vehicles.m_buffer[(int)frontVehicleId].Info; - - float attachOffset; - if ((vehicleData.m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0 != reversed) { - attachOffset = this.m_info.m_attachOffsetBack - this.m_info.m_generatedInfo.m_size.z * 0.5f; - } else { - attachOffset = this.m_info.m_attachOffsetFront - this.m_info.m_generatedInfo.m_size.z * 0.5f; - } - - float frontAttachOffset; - if ((vehMan.m_vehicles.m_buffer[(int)frontVehicleId].m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0 != reversed) { - frontAttachOffset = frontVehInfo.m_attachOffsetFront - frontVehInfo.m_generatedInfo.m_size.z * 0.5f; - } else { - frontAttachOffset = frontVehInfo.m_attachOffsetBack - frontVehInfo.m_generatedInfo.m_size.z * 0.5f; - } - - Vector3 posMinusAttachOffset = frameData.m_position; - if (reversed) { - posMinusAttachOffset += frameData.m_rotation * new Vector3(0f, 0f, attachOffset); - } else { - posMinusAttachOffset -= frameData.m_rotation * new Vector3(0f, 0f, attachOffset); - } - - Vector3 frontPosPlusAttachOffset = frontVehLastFrameData.m_position; - if (reversed) { - frontPosPlusAttachOffset -= frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontAttachOffset); - } else { - frontPosPlusAttachOffset += frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontAttachOffset); - } - - Vector3 frontPosMinusWheelBaseRot = frontVehLastFrameData.m_position; - wheelBaseRot = frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontVehInfo.m_generatedInfo.m_wheelBase * 0.5f); - if (reversed) { - frontPosMinusWheelBaseRot += wheelBaseRot; - } else { - frontPosMinusWheelBaseRot -= wheelBaseRot; - } - - if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - int someIndex = -1; - UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, 0, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); - beforeRotToTargetPos1DiffSqrMag = 0f; - } - - float maxAttachDist = Mathf.Max(Vector3.Distance(posMinusAttachOffset, frontPosPlusAttachOffset), 2f); - float one = 1f; - float maxAttachSqrDist = maxAttachDist * maxAttachDist; - float oneSqr = one * one; - int i = 0; - if (beforeRotToTargetPos1DiffSqrMag < maxAttachSqrDist) { - if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, posAfterWheelRot, posBeforeWheelRot, 0, ref leaderData, ref i, 1, 2, maxAttachSqrDist, oneSqr); - } - while (i < 4) { - vehicleData.SetTargetPos(i, vehicleData.GetTargetPos(i - 1)); - i++; - } - beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot; - beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude; - } - - if (vehicleData.m_path != 0u) { - NetManager netMan = Singleton.instance; - byte pathPosIndex = vehicleData.m_pathPositionIndex; - byte lastPathOffset = vehicleData.m_lastPathOffset; - if (pathPosIndex == 255) { - pathPosIndex = 0; - } - - PathManager pathMan = Singleton.instance; - PathUnit.Position curPathPos; - if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(pathPosIndex >> 1, out curPathPos)) { - netMan.m_segments.m_buffer[(int)curPathPos.m_segment].AddTraffic(Mathf.RoundToInt(this.m_info.m_generatedInfo.m_size.z * 3f), this.GetNoiseLevel()); - PathUnit.Position nextPathPos; // NON-STOCK CODE - if ((pathPosIndex & 1) == 0 || lastPathOffset == 0 || (leaderData.m_flags & Vehicle.Flags.WaitingPath) != (Vehicle.Flags)0) { - uint laneId = PathManager.GetLaneID(curPathPos); - if (laneId != 0u) { - netMan.m_lanes.m_buffer[laneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z); - } - } else if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(pathPosIndex >> 1, out nextPathPos)) { - // NON-STOCK CODE START - ushort transitNodeId; - if (curPathPos.m_offset < 128) { - transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_startNode; - } else { - transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_endNode; - } - - if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { - // NON-STOCK CODE END - - uint nextLaneId = PathManager.GetLaneID(nextPathPos); - if (nextLaneId != 0u) { - netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z); - } - } // NON-STOCK CODE - } - } - } - - beforeRotToTargetPos1Diff = curInvRot * beforeRotToTargetPos1Diff; - float negTotalAttachLen = -((this.m_info.m_generatedInfo.m_wheelBase + frontVehInfo.m_generatedInfo.m_wheelBase) * 0.5f + attachOffset + frontAttachOffset); - bool hasPath = false; - if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - float u1; - float u2; - if (Line3.Intersect(posBeforeWheelRot, vehicleData.m_targetPos1, frontPosMinusWheelBaseRot, negTotalAttachLen, out u1, out u2)) { - targetMotion = beforeRotToTargetPos1Diff * Mathf.Clamp(Mathf.Min(u1, u2) / 0.6f, 0f, 2f); - } else { - Line3.DistanceSqr(posBeforeWheelRot, vehicleData.m_targetPos1, frontPosMinusWheelBaseRot, out u1); - targetMotion = beforeRotToTargetPos1Diff * Mathf.Clamp(u1 / 0.6f, 0f, 2f); - } - hasPath = true; - } - - if (hasPath) { - if (Vector3.Dot(frontPosMinusWheelBaseRot - posBeforeWheelRot, posBeforeWheelRot - posAfterWheelRot) < 0f) { - motionFactor = 0f; - } - } else { - float frontPosBeforeToAfterWheelRotDist = Vector3.Distance(frontPosMinusWheelBaseRot, posBeforeWheelRot); - motionFactor = 0f; - targetMotion = curInvRot * ((frontPosMinusWheelBaseRot - posBeforeWheelRot) * (Mathf.Max(0f, frontPosBeforeToAfterWheelRotDist - negTotalAttachLen) / Mathf.Max(1f, frontPosBeforeToAfterWheelRotDist * 0.6f))); - } - } else { - float estimatedFrameDist = (curSpeed + acceleration) * (0.5f + 0.5f * (curSpeed + acceleration) / braking); - float maxSpeedAdd = Mathf.Max(curSpeed + acceleration, 2f); - float meanSpeedAdd = Mathf.Max((estimatedFrameDist - maxSpeedAdd) / 2f, 1f); - float maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; - float meanSpeedAddSqr = meanSpeedAdd * meanSpeedAdd; - if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { - int someIndex = -1; - UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, leaderID, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); - beforeRotToTargetPos1DiffSqrMag = 0f; - } - - int posIndex = 0; - bool flag3 = false; - if ((beforeRotToTargetPos1DiffSqrMag < maxSpeedAddSqr || vehicleData.m_targetPos3.w < 0.01f) && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { - if (vehicleData.m_path != 0u) { - UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, posAfterWheelRot, posBeforeWheelRot, leaderID, ref leaderData, ref posIndex, 1, 4, maxSpeedAddSqr, meanSpeedAddSqr); - } - if (posIndex < 4) { - flag3 = true; - while (posIndex < 4) { - vehicleData.SetTargetPos(posIndex, vehicleData.GetTargetPos(posIndex - 1)); - posIndex++; - } - } - beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot; - beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude; - } - - if ((leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0 && this.m_info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { - CustomForceTrafficLights(vehicleID, ref vehicleData, curSpeed > 0.1f); // NON-STOCK CODE - } - - if (vehicleData.m_path != 0u) { - NetManager netMan = Singleton.instance; - byte pathPosIndex = vehicleData.m_pathPositionIndex; - byte lastPathOffset = vehicleData.m_lastPathOffset; - if (pathPosIndex == 255) { - pathPosIndex = 0; - } - - PathManager pathMan = Singleton.instance; - PathUnit.Position curPathPos; - if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(pathPosIndex >> 1, out curPathPos)) { - netMan.m_segments.m_buffer[curPathPos.m_segment].AddTraffic(Mathf.RoundToInt(this.m_info.m_generatedInfo.m_size.z * 3f), this.GetNoiseLevel()); - PathUnit.Position nextPathPos; - if ((pathPosIndex & 1) == 0 || lastPathOffset == 0 || (leaderData.m_flags & Vehicle.Flags.WaitingPath) != (Vehicle.Flags)0) { - uint laneId = PathManager.GetLaneID(curPathPos); - if (laneId != 0u) { - netMan.m_lanes.m_buffer[laneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID); - } - } else if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(pathPosIndex >> 1, out nextPathPos)) { - // NON-STOCK CODE START - ushort transitNodeId; - if (curPathPos.m_offset < 128) { - transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_startNode; - } else { - transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_endNode; - } - - if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { - // NON-STOCK CODE END - - uint nextLaneId = PathManager.GetLaneID(nextPathPos); - if (nextLaneId != 0u) { - netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID); - } - } // NON-STOCK CODE - } - } - } - - float maxSpeed; - if ((leaderData.m_flags & Vehicle.Flags.Stopped) != (Vehicle.Flags)0) { - maxSpeed = 0f; - } else { - maxSpeed = Mathf.Min(vehicleData.m_targetPos1.w, GetMaxSpeed(leaderID, ref leaderData)); - } - - beforeRotToTargetPos1Diff = curInvRot * beforeRotToTargetPos1Diff; - if (reversed) { - beforeRotToTargetPos1Diff = -beforeRotToTargetPos1Diff; - } - - bool blocked = false; - float forwardLen = 0f; - if (beforeRotToTargetPos1DiffSqrMag > 1f) { - forward = VectorUtils.NormalizeXZ(beforeRotToTargetPos1Diff, out forwardLen); - if (forwardLen > 1f) { - Vector3 fwd = beforeRotToTargetPos1Diff; - maxSpeedAdd = Mathf.Max(curSpeed, 2f); - maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; - if (beforeRotToTargetPos1DiffSqrMag > maxSpeedAddSqr) { - float num20 = maxSpeedAdd / Mathf.Sqrt(beforeRotToTargetPos1DiffSqrMag); - fwd.x *= num20; - fwd.y *= num20; - } - - if (fwd.z < -1f) { - if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - Vector3 targetPos0TargetPos1Diff = vehicleData.m_targetPos1 - vehicleData.m_targetPos0; - targetPos0TargetPos1Diff = curInvRot * targetPos0TargetPos1Diff; - if (reversed) { - targetPos0TargetPos1Diff = -targetPos0TargetPos1Diff; - } - - if (targetPos0TargetPos1Diff.z < -0.01f) { - if (beforeRotToTargetPos1Diff.z < Mathf.Abs(beforeRotToTargetPos1Diff.x) * -10f) { - if (curSpeed < 0.01f) { - Reverse(leaderID, ref leaderData); - return; - } - - fwd.z = 0f; - beforeRotToTargetPos1Diff = Vector3.zero; - maxSpeed = 0f; - } else { - posBeforeWheelRot = posAfterWheelRot + Vector3.Normalize(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) * this.m_info.m_generatedInfo.m_wheelBase; - posIndex = -1; - UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, vehicleData.m_targetPos1, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) + 1f, 1f); - } - } else { - posIndex = -1; - UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); - vehicleData.m_targetPos1 = posBeforeWheelRot; - fwd.z = 0f; - beforeRotToTargetPos1Diff = Vector3.zero; - maxSpeed = 0f; - } - } - motionFactor = 0f; - } - - forward = VectorUtils.NormalizeXZ(fwd, out forwardLen); - float curve = Mathf.PI / 2f /* 1.57079637f*/ * (1f - forward.z); // <- constant: a bit inaccurate PI/2 - if (forwardLen > 1f) { - curve /= forwardLen; - } - - maxSpeed = Mathf.Min(maxSpeed, this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve)); - float targetDist = forwardLen; - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos2.w, braking)); - targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos2 - vehicleData.m_targetPos1); - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos3.w, braking)); - targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos3 - vehicleData.m_targetPos2); - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, 0f, braking)); - if (maxSpeed < curSpeed) { - float brake = Mathf.Max(acceleration, Mathf.Min(braking, curSpeed)); - targetSpeed = Mathf.Max(maxSpeed, curSpeed - brake); - } else { - float accel = Mathf.Max(acceleration, Mathf.Min(braking, -curSpeed)); - targetSpeed = Mathf.Min(maxSpeed, curSpeed + accel); - } - } - } else if (curSpeed < 0.1f && flag3 && vehicleInfo.m_vehicleAI.ArriveAtDestination(leaderID, ref leaderData)) { - leaderData.Unspawn(leaderID); - return; - } - - if ((leaderData.m_flags & Vehicle.Flags.Stopped) == (Vehicle.Flags)0 && maxSpeed < 0.1f) { - blocked = true; - } - - if (blocked) { - leaderData.m_blockCounter = (byte)Mathf.Min((int)(leaderData.m_blockCounter + 1), 255); - } else { - leaderData.m_blockCounter = 0; - } - - if (forwardLen > 1f) { - if (reversed) { - forward = -forward; - } - targetMotion = forward * targetSpeed; - } else { - if (reversed) { - beforeRotToTargetPos1Diff = -beforeRotToTargetPos1Diff; - } - Vector3 vel = Vector3.ClampMagnitude(beforeRotToTargetPos1Diff * 0.5f - curveTangent, braking); - targetMotion = curveTangent + vel; - } - } - - Vector3 springs = targetMotion - curveTangent; - Vector3 targetAfterWheelRotMotion = frameData.m_rotation * targetMotion; - Vector3 posAfterWheelRotToTargetDiff = Vector3.Normalize((Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) * (targetMotion.magnitude * motionFactor); - posBeforeWheelRot += targetAfterWheelRotMotion; - posAfterWheelRot += posAfterWheelRotToTargetDiff; - - Vector3 targetPos; - if (reversed) { - frameData.m_rotation = Quaternion.LookRotation(posAfterWheelRot - posBeforeWheelRot); - targetPos = posBeforeWheelRot + frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); - } else { - frameData.m_rotation = Quaternion.LookRotation(posBeforeWheelRot - posAfterWheelRot); - targetPos = posBeforeWheelRot - frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); - } - frameData.m_velocity = targetPos - frameData.m_position; - - if (frontVehicleId != 0) { - frameData.m_position += frameData.m_velocity * 0.6f; - } else { - frameData.m_position += frameData.m_velocity * 0.5f; - } - frameData.m_swayVelocity = frameData.m_swayVelocity * (1f - this.m_info.m_dampers) - springs * (1f - this.m_info.m_springs) - frameData.m_swayPosition * this.m_info.m_springs; - frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; - frameData.m_steerAngle = 0f; - frameData.m_travelDistance += targetMotion.z; - frameData.m_lightIntensity.x = ((!reversed) ? 5f : 0f); - frameData.m_lightIntensity.y = ((!reversed) ? 0f : 5f); - frameData.m_lightIntensity.z = 0f; - frameData.m_lightIntensity.w = 0f; - frameData.m_underground = ((vehicleData.m_flags & Vehicle.Flags.Underground) != (Vehicle.Flags)0); - frameData.m_transition = ((vehicleData.m_flags & Vehicle.Flags.Transition) != (Vehicle.Flags)0); - //base.SimulationStep(vehicleID, ref vehicleData, ref frameData, leaderID, ref leaderData, lodPhysics); - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) { - NetManager instance = Singleton.instance; - instance.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); - NetInfo info = instance.m_segments.m_buffer[(int)position.m_segment].Info; - if (info.m_lanes != null && info.m_lanes.Length > (int)position.m_lane) { - float laneSpeedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info.m_lanes[position.m_lane]) : info.m_lanes[position.m_lane].m_speedLimit; - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, laneSpeedLimit, instance.m_lanes.m_buffer[laneID].m_curve); - } else { - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); - } - } - - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { - /// NON-STOCK CODE START /// - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); - if (vehicleType == ExtVehicleType.None) { +namespace TrafficManager.Custom.AI { + using System; + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using Traffic.Data; + using UnityEngine; + + [TargetType(typeof(TrainAI))] + public class CustomTrainAI : TrainAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + + if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) { + byte pathFindFlags = Singleton.instance.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags; + + if ((pathFindFlags & PathUnit.FLAG_READY) != 0) { + try { + this.PathFindReady(vehicleId, ref vehicleData); + } catch (Exception e) { + Log.Warning($"TrainAI.PathFindReady({vehicleId}) for vehicle {vehicleData.Info?.m_class?.name} threw an exception: {e.ToString()}"); + vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; + Singleton.instance.ReleasePath(vehicleData.m_path); + vehicleData.m_path = 0u; + vehicleData.Unspawn(vehicleId); + return; + } + } else if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) { + vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; + Singleton.instance.ReleasePath(vehicleData.m_path); + vehicleData.m_path = 0u; + vehicleData.Unspawn(vehicleId); + return; + } + } else { + if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) { + this.TrySpawn(vehicleId, ref vehicleData); + } + } + + // NON-STOCK CODE START + extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); + + if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { + // Advanced AI traffic measurement + extVehicleMan.LogTraffic(vehicleId, ref vehicleData); + } + // NON-STOCK CODE END + + bool reversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0; + ushort connectedVehicleId; + if (reversed) { + connectedVehicleId = vehicleData.GetLastVehicle(vehicleId); + } else { + connectedVehicleId = vehicleId; + } + + VehicleManager instance = Singleton.instance; + VehicleInfo info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; + info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { + return; + } + bool newReversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0; + if (newReversed != reversed) { + reversed = newReversed; + if (reversed) { + connectedVehicleId = vehicleData.GetLastVehicle(vehicleId); + } else { + connectedVehicleId = vehicleId; + } + info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; + info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { + return; + } + newReversed = ((vehicleData.m_flags & Vehicle.Flags.Reversed) != 0); + if (newReversed != reversed) { + Singleton.instance.ReleaseVehicle(vehicleId); + return; + } + } + if (reversed) { + connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_leadingVehicle; + int num2 = 0; + while (connectedVehicleId != 0) { + info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; + info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { + return; + } + connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_leadingVehicle; + if (++num2 > 16384) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } else { + connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_trailingVehicle; + int num3 = 0; + while (connectedVehicleId != 0) { + info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info; + info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0); + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { + return; + } + connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_trailingVehicle; + if (++num3 > 16384) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } + if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace | Vehicle.Flags.WaitingCargo)) == 0) { + Singleton.instance.ReleaseVehicle(vehicleId); + } else if (vehicleData.m_blockCounter == 255) { + // NON-STOCK CODE START + if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { + // NON-STOCK CODE END + Singleton.instance.ReleaseVehicle(vehicleId); + } // NON-STOCK CODE + } + } + + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ushort leaderID, ref Vehicle leaderData, int lodPhysics) { + bool reversed = (leaderData.m_flags & Vehicle.Flags.Reversed) != (Vehicle.Flags)0; + ushort frontVehicleId = (!reversed) ? vehicleData.m_leadingVehicle : vehicleData.m_trailingVehicle; + VehicleInfo vehicleInfo; + if (leaderID != vehicleID) { + vehicleInfo = leaderData.Info; + } else { + vehicleInfo = this.m_info; + } + TrainAI trainAI = vehicleInfo.m_vehicleAI as TrainAI; + if (frontVehicleId != 0) { + frameData.m_position += frameData.m_velocity * 0.4f; + } else { + frameData.m_position += frameData.m_velocity * 0.5f; + } + frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; + + Vector3 posBeforeWheelRot = frameData.m_position; + Vector3 posAfterWheelRot = frameData.m_position; + Vector3 wheelBaseRot = frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); + if (reversed) { + posBeforeWheelRot -= wheelBaseRot; + posAfterWheelRot += wheelBaseRot; + } else { + posBeforeWheelRot += wheelBaseRot; + posAfterWheelRot -= wheelBaseRot; + } + + float acceleration = this.m_info.m_acceleration; + float braking = this.m_info.m_braking; + float curSpeed = frameData.m_velocity.magnitude; + + Vector3 beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot; + float beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude; + + Quaternion curInvRot = Quaternion.Inverse(frameData.m_rotation); + Vector3 curveTangent = curInvRot * frameData.m_velocity; + + Vector3 forward = Vector3.forward; + Vector3 targetMotion = Vector3.zero; + float targetSpeed = 0f; + float motionFactor = 0.5f; + + if (frontVehicleId != 0) { + VehicleManager vehMan = Singleton.instance; + Vehicle.Frame frontVehLastFrameData = vehMan.m_vehicles.m_buffer[(int)frontVehicleId].GetLastFrameData(); + VehicleInfo frontVehInfo = vehMan.m_vehicles.m_buffer[(int)frontVehicleId].Info; + + float attachOffset; + if ((vehicleData.m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0 != reversed) { + attachOffset = this.m_info.m_attachOffsetBack - this.m_info.m_generatedInfo.m_size.z * 0.5f; + } else { + attachOffset = this.m_info.m_attachOffsetFront - this.m_info.m_generatedInfo.m_size.z * 0.5f; + } + + float frontAttachOffset; + if ((vehMan.m_vehicles.m_buffer[(int)frontVehicleId].m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0 != reversed) { + frontAttachOffset = frontVehInfo.m_attachOffsetFront - frontVehInfo.m_generatedInfo.m_size.z * 0.5f; + } else { + frontAttachOffset = frontVehInfo.m_attachOffsetBack - frontVehInfo.m_generatedInfo.m_size.z * 0.5f; + } + + Vector3 posMinusAttachOffset = frameData.m_position; + if (reversed) { + posMinusAttachOffset += frameData.m_rotation * new Vector3(0f, 0f, attachOffset); + } else { + posMinusAttachOffset -= frameData.m_rotation * new Vector3(0f, 0f, attachOffset); + } + + Vector3 frontPosPlusAttachOffset = frontVehLastFrameData.m_position; + if (reversed) { + frontPosPlusAttachOffset -= frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontAttachOffset); + } else { + frontPosPlusAttachOffset += frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontAttachOffset); + } + + Vector3 frontPosMinusWheelBaseRot = frontVehLastFrameData.m_position; + wheelBaseRot = frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontVehInfo.m_generatedInfo.m_wheelBase * 0.5f); + if (reversed) { + frontPosMinusWheelBaseRot += wheelBaseRot; + } else { + frontPosMinusWheelBaseRot -= wheelBaseRot; + } + + if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + int someIndex = -1; + UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, 0, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); + beforeRotToTargetPos1DiffSqrMag = 0f; + } + + float maxAttachDist = Mathf.Max(Vector3.Distance(posMinusAttachOffset, frontPosPlusAttachOffset), 2f); + float one = 1f; + float maxAttachSqrDist = maxAttachDist * maxAttachDist; + float oneSqr = one * one; + int i = 0; + if (beforeRotToTargetPos1DiffSqrMag < maxAttachSqrDist) { + if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, posAfterWheelRot, posBeforeWheelRot, 0, ref leaderData, ref i, 1, 2, maxAttachSqrDist, oneSqr); + } + while (i < 4) { + vehicleData.SetTargetPos(i, vehicleData.GetTargetPos(i - 1)); + i++; + } + beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot; + beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude; + } + + if (vehicleData.m_path != 0u) { + NetManager netMan = Singleton.instance; + byte pathPosIndex = vehicleData.m_pathPositionIndex; + byte lastPathOffset = vehicleData.m_lastPathOffset; + if (pathPosIndex == 255) { + pathPosIndex = 0; + } + + PathManager pathMan = Singleton.instance; + PathUnit.Position curPathPos; + if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(pathPosIndex >> 1, out curPathPos)) { + netMan.m_segments.m_buffer[(int)curPathPos.m_segment].AddTraffic(Mathf.RoundToInt(this.m_info.m_generatedInfo.m_size.z * 3f), this.GetNoiseLevel()); + PathUnit.Position nextPathPos; // NON-STOCK CODE + if ((pathPosIndex & 1) == 0 || lastPathOffset == 0 || (leaderData.m_flags & Vehicle.Flags.WaitingPath) != (Vehicle.Flags)0) { + uint laneId = PathManager.GetLaneID(curPathPos); + if (laneId != 0u) { + netMan.m_lanes.m_buffer[laneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z); + } + } else if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(pathPosIndex >> 1, out nextPathPos)) { + // NON-STOCK CODE START + ushort transitNodeId; + if (curPathPos.m_offset < 128) { + transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_startNode; + } else { + transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_endNode; + } + + if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { + // NON-STOCK CODE END + + uint nextLaneId = PathManager.GetLaneID(nextPathPos); + if (nextLaneId != 0u) { + netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z); + } + } // NON-STOCK CODE + } + } + } + + beforeRotToTargetPos1Diff = curInvRot * beforeRotToTargetPos1Diff; + float negTotalAttachLen = -((this.m_info.m_generatedInfo.m_wheelBase + frontVehInfo.m_generatedInfo.m_wheelBase) * 0.5f + attachOffset + frontAttachOffset); + bool hasPath = false; + if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + float u1; + float u2; + if (Line3.Intersect(posBeforeWheelRot, vehicleData.m_targetPos1, frontPosMinusWheelBaseRot, negTotalAttachLen, out u1, out u2)) { + targetMotion = beforeRotToTargetPos1Diff * Mathf.Clamp(Mathf.Min(u1, u2) / 0.6f, 0f, 2f); + } else { + Line3.DistanceSqr(posBeforeWheelRot, vehicleData.m_targetPos1, frontPosMinusWheelBaseRot, out u1); + targetMotion = beforeRotToTargetPos1Diff * Mathf.Clamp(u1 / 0.6f, 0f, 2f); + } + hasPath = true; + } + + if (hasPath) { + if (Vector3.Dot(frontPosMinusWheelBaseRot - posBeforeWheelRot, posBeforeWheelRot - posAfterWheelRot) < 0f) { + motionFactor = 0f; + } + } else { + float frontPosBeforeToAfterWheelRotDist = Vector3.Distance(frontPosMinusWheelBaseRot, posBeforeWheelRot); + motionFactor = 0f; + targetMotion = curInvRot * ((frontPosMinusWheelBaseRot - posBeforeWheelRot) * (Mathf.Max(0f, frontPosBeforeToAfterWheelRotDist - negTotalAttachLen) / Mathf.Max(1f, frontPosBeforeToAfterWheelRotDist * 0.6f))); + } + } else { + float estimatedFrameDist = (curSpeed + acceleration) * (0.5f + 0.5f * (curSpeed + acceleration) / braking); + float maxSpeedAdd = Mathf.Max(curSpeed + acceleration, 2f); + float meanSpeedAdd = Mathf.Max((estimatedFrameDist - maxSpeedAdd) / 2f, 1f); + float maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; + float meanSpeedAddSqr = meanSpeedAdd * meanSpeedAdd; + if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { + int someIndex = -1; + UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, leaderID, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); + beforeRotToTargetPos1DiffSqrMag = 0f; + } + + int posIndex = 0; + bool flag3 = false; + if ((beforeRotToTargetPos1DiffSqrMag < maxSpeedAddSqr || vehicleData.m_targetPos3.w < 0.01f) && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { + if (vehicleData.m_path != 0u) { + UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, posAfterWheelRot, posBeforeWheelRot, leaderID, ref leaderData, ref posIndex, 1, 4, maxSpeedAddSqr, meanSpeedAddSqr); + } + if (posIndex < 4) { + flag3 = true; + while (posIndex < 4) { + vehicleData.SetTargetPos(posIndex, vehicleData.GetTargetPos(posIndex - 1)); + posIndex++; + } + } + beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot; + beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude; + } + + if ((leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0 && this.m_info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { + CustomForceTrafficLights(vehicleID, ref vehicleData, curSpeed > 0.1f); // NON-STOCK CODE + } + + if (vehicleData.m_path != 0u) { + NetManager netMan = Singleton.instance; + byte pathPosIndex = vehicleData.m_pathPositionIndex; + byte lastPathOffset = vehicleData.m_lastPathOffset; + if (pathPosIndex == 255) { + pathPosIndex = 0; + } + + PathManager pathMan = Singleton.instance; + PathUnit.Position curPathPos; + if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(pathPosIndex >> 1, out curPathPos)) { + netMan.m_segments.m_buffer[curPathPos.m_segment].AddTraffic(Mathf.RoundToInt(this.m_info.m_generatedInfo.m_size.z * 3f), this.GetNoiseLevel()); + PathUnit.Position nextPathPos; + if ((pathPosIndex & 1) == 0 || lastPathOffset == 0 || (leaderData.m_flags & Vehicle.Flags.WaitingPath) != (Vehicle.Flags)0) { + uint laneId = PathManager.GetLaneID(curPathPos); + if (laneId != 0u) { + netMan.m_lanes.m_buffer[laneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID); + } + } else if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(pathPosIndex >> 1, out nextPathPos)) { + // NON-STOCK CODE START + ushort transitNodeId; + if (curPathPos.m_offset < 128) { + transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_startNode; + } else { + transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_endNode; + } + + if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { + // NON-STOCK CODE END + + uint nextLaneId = PathManager.GetLaneID(nextPathPos); + if (nextLaneId != 0u) { + netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID); + } + } // NON-STOCK CODE + } + } + } + + float maxSpeed; + if ((leaderData.m_flags & Vehicle.Flags.Stopped) != (Vehicle.Flags)0) { + maxSpeed = 0f; + } else { + maxSpeed = Mathf.Min(vehicleData.m_targetPos1.w, GetMaxSpeed(leaderID, ref leaderData)); + } + + beforeRotToTargetPos1Diff = curInvRot * beforeRotToTargetPos1Diff; + if (reversed) { + beforeRotToTargetPos1Diff = -beforeRotToTargetPos1Diff; + } + + bool blocked = false; + float forwardLen = 0f; + if (beforeRotToTargetPos1DiffSqrMag > 1f) { + forward = VectorUtils.NormalizeXZ(beforeRotToTargetPos1Diff, out forwardLen); + if (forwardLen > 1f) { + Vector3 fwd = beforeRotToTargetPos1Diff; + maxSpeedAdd = Mathf.Max(curSpeed, 2f); + maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; + if (beforeRotToTargetPos1DiffSqrMag > maxSpeedAddSqr) { + float num20 = maxSpeedAdd / Mathf.Sqrt(beforeRotToTargetPos1DiffSqrMag); + fwd.x *= num20; + fwd.y *= num20; + } + + if (fwd.z < -1f) { + if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + Vector3 targetPos0TargetPos1Diff = vehicleData.m_targetPos1 - vehicleData.m_targetPos0; + targetPos0TargetPos1Diff = curInvRot * targetPos0TargetPos1Diff; + if (reversed) { + targetPos0TargetPos1Diff = -targetPos0TargetPos1Diff; + } + + if (targetPos0TargetPos1Diff.z < -0.01f) { + if (beforeRotToTargetPos1Diff.z < Mathf.Abs(beforeRotToTargetPos1Diff.x) * -10f) { + if (curSpeed < 0.01f) { + Reverse(leaderID, ref leaderData); + return; + } + + fwd.z = 0f; + beforeRotToTargetPos1Diff = Vector3.zero; + maxSpeed = 0f; + } else { + posBeforeWheelRot = posAfterWheelRot + Vector3.Normalize(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) * this.m_info.m_generatedInfo.m_wheelBase; + posIndex = -1; + UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, vehicleData.m_targetPos1, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) + 1f, 1f); + } + } else { + posIndex = -1; + UpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); + vehicleData.m_targetPos1 = posBeforeWheelRot; + fwd.z = 0f; + beforeRotToTargetPos1Diff = Vector3.zero; + maxSpeed = 0f; + } + } + motionFactor = 0f; + } + + forward = VectorUtils.NormalizeXZ(fwd, out forwardLen); + float curve = Mathf.PI / 2f /* 1.57079637f*/ * (1f - forward.z); // <- constant: a bit inaccurate PI/2 + if (forwardLen > 1f) { + curve /= forwardLen; + } + + maxSpeed = Mathf.Min(maxSpeed, this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve)); + float targetDist = forwardLen; + maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos2.w, braking)); + targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos2 - vehicleData.m_targetPos1); + maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos3.w, braking)); + targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos3 - vehicleData.m_targetPos2); + maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, 0f, braking)); + if (maxSpeed < curSpeed) { + float brake = Mathf.Max(acceleration, Mathf.Min(braking, curSpeed)); + targetSpeed = Mathf.Max(maxSpeed, curSpeed - brake); + } else { + float accel = Mathf.Max(acceleration, Mathf.Min(braking, -curSpeed)); + targetSpeed = Mathf.Min(maxSpeed, curSpeed + accel); + } + } + } else if (curSpeed < 0.1f && flag3 && vehicleInfo.m_vehicleAI.ArriveAtDestination(leaderID, ref leaderData)) { + leaderData.Unspawn(leaderID); + return; + } + + if ((leaderData.m_flags & Vehicle.Flags.Stopped) == (Vehicle.Flags)0 && maxSpeed < 0.1f) { + blocked = true; + } + + if (blocked) { + leaderData.m_blockCounter = (byte)Mathf.Min((int)(leaderData.m_blockCounter + 1), 255); + } else { + leaderData.m_blockCounter = 0; + } + + if (forwardLen > 1f) { + if (reversed) { + forward = -forward; + } + targetMotion = forward * targetSpeed; + } else { + if (reversed) { + beforeRotToTargetPos1Diff = -beforeRotToTargetPos1Diff; + } + Vector3 vel = Vector3.ClampMagnitude(beforeRotToTargetPos1Diff * 0.5f - curveTangent, braking); + targetMotion = curveTangent + vel; + } + } + + Vector3 springs = targetMotion - curveTangent; + Vector3 targetAfterWheelRotMotion = frameData.m_rotation * targetMotion; + Vector3 posAfterWheelRotToTargetDiff = Vector3.Normalize((Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) * (targetMotion.magnitude * motionFactor); + posBeforeWheelRot += targetAfterWheelRotMotion; + posAfterWheelRot += posAfterWheelRotToTargetDiff; + + Vector3 targetPos; + if (reversed) { + frameData.m_rotation = Quaternion.LookRotation(posAfterWheelRot - posBeforeWheelRot); + targetPos = posBeforeWheelRot + frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); + } else { + frameData.m_rotation = Quaternion.LookRotation(posBeforeWheelRot - posAfterWheelRot); + targetPos = posBeforeWheelRot - frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); + } + frameData.m_velocity = targetPos - frameData.m_position; + + if (frontVehicleId != 0) { + frameData.m_position += frameData.m_velocity * 0.6f; + } else { + frameData.m_position += frameData.m_velocity * 0.5f; + } + frameData.m_swayVelocity = frameData.m_swayVelocity * (1f - this.m_info.m_dampers) - springs * (1f - this.m_info.m_springs) - frameData.m_swayPosition * this.m_info.m_springs; + frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; + frameData.m_steerAngle = 0f; + frameData.m_travelDistance += targetMotion.z; + frameData.m_lightIntensity.x = ((!reversed) ? 5f : 0f); + frameData.m_lightIntensity.y = ((!reversed) ? 0f : 5f); + frameData.m_lightIntensity.z = 0f; + frameData.m_lightIntensity.w = 0f; + frameData.m_underground = ((vehicleData.m_flags & Vehicle.Flags.Underground) != (Vehicle.Flags)0); + frameData.m_transition = ((vehicleData.m_flags & Vehicle.Flags.Transition) != (Vehicle.Flags)0); + //base.SimulationStep(vehicleID, ref vehicleData, ref frameData, leaderID, ref leaderData, lodPhysics); + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) { + NetManager instance = Singleton.instance; + instance.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); + NetInfo info = instance.m_segments.m_buffer[(int)position.m_segment].Info; + if (info.m_lanes != null && info.m_lanes.Length > (int)position.m_lane) { + float laneSpeedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info.m_lanes[position.m_lane]) : info.m_lanes[position.m_lane].m_speedLimit; + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, laneSpeedLimit, instance.m_lanes.m_buffer[laneID].m_curve); + } else { + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); + } + } + + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { + /// NON-STOCK CODE START /// + ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); + if (vehicleType == ExtVehicleType.None) { #if DEBUG - Log.Warning($"CustomTrainAI.CustomStartPathFind: Vehicle {vehicleID} does not have a valid vehicle type!"); + Log.Warning($"CustomTrainAI.CustomStartPathFind: Vehicle {vehicleID} does not have a valid vehicle type!"); #endif - vehicleType = ExtVehicleType.RailVehicle; - } else if (vehicleType == ExtVehicleType.CargoTrain) { - vehicleType = ExtVehicleType.CargoVehicle; - } - /// NON-STOCK CODE END /// - - VehicleInfo info = this.m_info; - if ((vehicleData.m_flags & Vehicle.Flags.Spawned) == 0 && Vector3.Distance(startPos, endPos) < 100f) { - startPos = endPos; - } - bool allowUnderground; - bool allowUnderground2; - if (info.m_vehicleType == VehicleInfo.VehicleType.Metro) { - allowUnderground = true; - allowUnderground2 = true; - } else { - allowUnderground = ((vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0); - allowUnderground2 = false; - } - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startSqrDistA; - float startSqrDistB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endSqrDistA; - float endSqrDistB; - if (CustomPathManager.FindPathPosition(startPos, this.m_transportInfo.m_netService, this.m_transportInfo.m_secondaryNetService, NetInfo.LaneType.Vehicle, info.m_vehicleType, VehicleInfo.VehicleType.None, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && - CustomPathManager.FindPathPosition(endPos, this.m_transportInfo.m_netService, this.m_transportInfo.m_secondaryNetService, NetInfo.LaneType.Vehicle, info.m_vehicleType, VehicleInfo.VehicleType.None, allowUnderground2, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { - if (!startBothWays || startSqrDistB > startSqrDistA * 1.2f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endSqrDistB > endSqrDistA * 1.2f) { - endPosB = default(PathUnit.Position); - } - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = vehicleType; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = false; - args.hasCombustionEngine = false; - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = true; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } - - [RedirectMethod] - public void CustomCheckNextLane(ushort vehicleId, ref Vehicle vehicleData, ref float maxSpeed, PathUnit.Position nextPosition, uint nextLaneId, byte nextOffset, PathUnit.Position refPosition, uint refLaneId, byte refOffset, Bezier3 bezier) { - NetManager netManager = Singleton.instance; - - ushort nextSourceNodeId; - if (nextOffset < nextPosition.m_offset) { - nextSourceNodeId = netManager.m_segments.m_buffer[(int)nextPosition.m_segment].m_startNode; - } else { - nextSourceNodeId = netManager.m_segments.m_buffer[(int)nextPosition.m_segment].m_endNode; - } - - ushort refTargetNodeId; - if (refOffset == 0) { - refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_startNode; - } else { - refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_endNode; - } + vehicleType = ExtVehicleType.RailVehicle; + } else if (vehicleType == ExtVehicleType.CargoTrain) { + vehicleType = ExtVehicleType.CargoVehicle; + } + /// NON-STOCK CODE END /// + + VehicleInfo info = this.m_info; + if ((vehicleData.m_flags & Vehicle.Flags.Spawned) == 0 && Vector3.Distance(startPos, endPos) < 100f) { + startPos = endPos; + } + bool allowUnderground; + bool allowUnderground2; + if (info.m_vehicleType == VehicleInfo.VehicleType.Metro) { + allowUnderground = true; + allowUnderground2 = true; + } else { + allowUnderground = ((vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0); + allowUnderground2 = false; + } + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startSqrDistA; + float startSqrDistB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endSqrDistA; + float endSqrDistB; + if (CustomPathManager.FindPathPosition(startPos, this.m_transportInfo.m_netService, this.m_transportInfo.m_secondaryNetService, NetInfo.LaneType.Vehicle, info.m_vehicleType, VehicleInfo.VehicleType.None, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && + CustomPathManager.FindPathPosition(endPos, this.m_transportInfo.m_netService, this.m_transportInfo.m_secondaryNetService, NetInfo.LaneType.Vehicle, info.m_vehicleType, VehicleInfo.VehicleType.None, allowUnderground2, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { + if (!startBothWays || startSqrDistB > startSqrDistA * 1.2f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endSqrDistB > endSqrDistA * 1.2f) { + endPosB = default(PathUnit.Position); + } + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = vehicleType; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = false; + args.hasCombustionEngine = false; + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = true; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } + + [RedirectMethod] + public void CustomCheckNextLane(ushort vehicleId, ref Vehicle vehicleData, ref float maxSpeed, PathUnit.Position nextPosition, uint nextLaneId, byte nextOffset, PathUnit.Position refPosition, uint refLaneId, byte refOffset, Bezier3 bezier) { + NetManager netManager = Singleton.instance; + + ushort nextSourceNodeId; + if (nextOffset < nextPosition.m_offset) { + nextSourceNodeId = netManager.m_segments.m_buffer[(int)nextPosition.m_segment].m_startNode; + } else { + nextSourceNodeId = netManager.m_segments.m_buffer[(int)nextPosition.m_segment].m_endNode; + } + + ushort refTargetNodeId; + if (refOffset == 0) { + refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_startNode; + } else { + refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_endNode; + } #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[21] && (GlobalConfig.Instance.Debug.NodeId <= 0 || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) && (GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.None || GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.RailVehicle) && (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId); - - if (debug) { - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}) called.\n" + - $"\trefPosition.m_segment={refPosition.m_segment}, refPosition.m_offset={refPosition.m_offset}\n" + - $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + - $"\trefLaneId={refLaneId}, refOffset={refOffset}\n" + - $"\tprevLaneId={nextLaneId}, prevOffset={nextOffset}\n" + - $"\tnextSourceNodeId={nextSourceNodeId}\n" + - $"\trefTargetNodeId={refTargetNodeId}, refTargetNodeId={refTargetNodeId}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[21] + && (GlobalConfig.Instance.Debug.NodeId <= 0 + || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) + && (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.RailVehicle) + && (GlobalConfig.Instance.Debug.VehicleId == 0 + || GlobalConfig.Instance.Debug.VehicleId == vehicleId); + + if (debug) { + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}) called.\n" + + $"\trefPosition.m_segment={refPosition.m_segment}, refPosition.m_offset={refPosition.m_offset}\n" + + $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + + $"\trefLaneId={refLaneId}, refOffset={refOffset}\n" + + $"\tprevLaneId={nextLaneId}, prevOffset={nextOffset}\n" + + $"\tnextSourceNodeId={nextSourceNodeId}\n" + + $"\trefTargetNodeId={refTargetNodeId}, refTargetNodeId={refTargetNodeId}"); + } #endif - Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); - float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; + Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); + float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; - Vector3 lastPosPlusRot = lastFrameData.m_position; - Vector3 lastPosMinusRot = lastFrameData.m_position; - Vector3 rotationAdd = lastFrameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); - lastPosPlusRot += rotationAdd; - lastPosMinusRot -= rotationAdd; - float breakingDist = 0.5f * sqrVelocity / this.m_info.m_braking; - float distToTargetAfterRot = Vector3.Distance(lastPosPlusRot, bezier.a); - float distToTargetBeforeRot = Vector3.Distance(lastPosMinusRot, bezier.a); + Vector3 lastPosPlusRot = lastFrameData.m_position; + Vector3 lastPosMinusRot = lastFrameData.m_position; + Vector3 rotationAdd = lastFrameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); + lastPosPlusRot += rotationAdd; + lastPosMinusRot -= rotationAdd; + float breakingDist = 0.5f * sqrVelocity / this.m_info.m_braking; + float distToTargetAfterRot = Vector3.Distance(lastPosPlusRot, bezier.a); + float distToTargetBeforeRot = Vector3.Distance(lastPosMinusRot, bezier.a); #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): lastPos={lastFrameData.m_position} lastPosMinusRot={lastPosMinusRot} lastPosPlusRot={lastPosPlusRot} rotationAdd={rotationAdd} breakingDist={breakingDist} distToTargetAfterRot={distToTargetAfterRot} distToTargetBeforeRot={distToTargetBeforeRot}"); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): lastPos={lastFrameData.m_position} lastPosMinusRot={lastPosMinusRot} lastPosPlusRot={lastPosPlusRot} rotationAdd={rotationAdd} breakingDist={breakingDist} distToTargetAfterRot={distToTargetAfterRot} distToTargetBeforeRot={distToTargetBeforeRot}"); #endif - if (Mathf.Min(distToTargetAfterRot, distToTargetBeforeRot) >= breakingDist - 5f) { - /*VehicleManager vehMan = Singleton.instance; - ushort firstVehicleId = vehicleData.GetFirstVehicle(vehicleId); - if (VehicleBehaviorManager.Instance.MayDespawn(ref vehMan.m_vehicles.m_buffer[firstVehicleId]) || vehMan.m_vehicles.m_buffer[firstVehicleId].m_blockCounter < 100) {*/ // NON-STOCK CODE + if (Mathf.Min(distToTargetAfterRot, distToTargetBeforeRot) >= breakingDist - 5f) { + /*VehicleManager vehMan = Singleton.instance; + ushort firstVehicleId = vehicleData.GetFirstVehicle(vehicleId); + if (VehicleBehaviorManager.Instance.MayDespawn(ref vehMan.m_vehicles.m_buffer[firstVehicleId]) || vehMan.m_vehicles.m_buffer[firstVehicleId].m_blockCounter < 100) {*/ // NON-STOCK CODE #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for free space on lane {nextLaneId}."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for free space on lane {nextLaneId}."); #endif - if (!netManager.m_lanes.m_buffer[nextLaneId].CheckSpace(1000f, vehicleId)) { + if (!netManager.m_lanes.m_buffer[nextLaneId].CheckSpace(1000f, vehicleId)) { #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): No space available on lane {nextLaneId}. ABORT."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): No space available on lane {nextLaneId}. ABORT."); #endif - vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; - vehicleData.m_waitCounter = 0; - maxSpeed = 0f; - return; - } - - Vector3 bezierMiddlePoint = bezier.Position(0.5f); - Segment3 segment; - if (Vector3.SqrMagnitude(vehicleData.m_segment.a - bezierMiddlePoint) < Vector3.SqrMagnitude(bezier.a - bezierMiddlePoint)) { - segment = new Segment3(vehicleData.m_segment.a, bezierMiddlePoint); - } else { - segment = new Segment3(bezier.a, bezierMiddlePoint); - } + vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; + vehicleData.m_waitCounter = 0; + maxSpeed = 0f; + return; + } + + Vector3 bezierMiddlePoint = bezier.Position(0.5f); + Segment3 segment; + if (Vector3.SqrMagnitude(vehicleData.m_segment.a - bezierMiddlePoint) < Vector3.SqrMagnitude(bezier.a - bezierMiddlePoint)) { + segment = new Segment3(vehicleData.m_segment.a, bezierMiddlePoint); + } else { + segment = new Segment3(bezier.a, bezierMiddlePoint); + } #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for overlap (1). segment.a={segment.a} segment.b={segment.b}"); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for overlap (1). segment.a={segment.a} segment.b={segment.b}"); #endif - if (segment.LengthSqr() >= 3f) { - segment.a += (segment.b - segment.a).normalized * 2.5f; - if (CustomTrainAI.CheckOverlap(vehicleId, ref vehicleData, segment, vehicleId)) { + if (segment.LengthSqr() >= 3f) { + segment.a += (segment.b - segment.a).normalized * 2.5f; + if (CustomTrainAI.CheckOverlap(vehicleId, ref vehicleData, segment, vehicleId)) { #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Overlap detected (1). segment.LengthSqr()={segment.LengthSqr()} segment.a={segment.a} ABORT."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Overlap detected (1). segment.LengthSqr()={segment.LengthSqr()} segment.a={segment.a} ABORT."); #endif - vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; - vehicleData.m_waitCounter = 0; - maxSpeed = 0f; - return; - } - } - - segment = new Segment3(bezierMiddlePoint, bezier.d); + vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; + vehicleData.m_waitCounter = 0; + maxSpeed = 0f; + return; + } + } + + segment = new Segment3(bezierMiddlePoint, bezier.d); #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for overlap (2). segment.a={segment.a} segment.b={segment.b}"); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for overlap (2). segment.a={segment.a} segment.b={segment.b}"); #endif - if (segment.LengthSqr() >= 1f && CustomTrainAI.CheckOverlap(vehicleId, ref vehicleData, segment, vehicleId)) { + if (segment.LengthSqr() >= 1f && CustomTrainAI.CheckOverlap(vehicleId, ref vehicleData, segment, vehicleId)) { #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Overlap detected (2). ABORT."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Overlap detected (2). ABORT."); #endif - vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; - vehicleData.m_waitCounter = 0; - maxSpeed = 0f; - return; - } - //} // NON-STOCK CODE - - //if (this.m_info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { // NON-STOCK CODE - if (nextSourceNodeId == refTargetNodeId) { + vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; + vehicleData.m_waitCounter = 0; + maxSpeed = 0f; + return; + } + //} // NON-STOCK CODE + + //if (this.m_info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { // NON-STOCK CODE + if (nextSourceNodeId == refTargetNodeId) { #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking if vehicle is allowed to change segment."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking if vehicle is allowed to change segment."); #endif - float oldMaxSpeed = maxSpeed; - if (! VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref refPosition, ref netManager.m_segments.m_buffer[refPosition.m_segment], refTargetNodeId, refLaneId, ref nextPosition, refTargetNodeId, ref netManager.m_nodes.m_buffer[refTargetNodeId], nextLaneId)) { + float oldMaxSpeed = maxSpeed; + if (! VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref refPosition, ref netManager.m_segments.m_buffer[refPosition.m_segment], refTargetNodeId, refLaneId, ref nextPosition, refTargetNodeId, ref netManager.m_nodes.m_buffer[refTargetNodeId], nextLaneId)) { #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Vehicle is NOT allowed to change segment. ABORT."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Vehicle is NOT allowed to change segment. ABORT."); #endif - maxSpeed = 0; - return; - } else { - ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); - } - maxSpeed = oldMaxSpeed; - } - //} // NON-STOCK CODE - } - } - - [RedirectMethod] - private void CustomForceTrafficLights(ushort vehicleID, ref Vehicle vehicleData, bool reserveSpace) { - uint pathUnitId = vehicleData.m_path; - if (pathUnitId != 0u) { - NetManager netMan = Singleton.instance; - PathManager pathMan = Singleton.instance; - byte pathPosIndex = vehicleData.m_pathPositionIndex; - if (pathPosIndex == 255) { - pathPosIndex = 0; - } - pathPosIndex = (byte)(pathPosIndex >> 1); - bool stopLoop = false; // NON-STOCK CODE - for (int i = 0; i < 6; i++) { - PathUnit.Position position; - if (!pathMan.m_pathUnits.m_buffer[pathUnitId].GetPosition(pathPosIndex, out position)) { - return; - } - - // NON-STOCK CODE START - ushort transitNodeId; - if (position.m_offset < 128) { - transitNodeId = netMan.m_segments.m_buffer[position.m_segment].m_startNode; - } else { - transitNodeId = netMan.m_segments.m_buffer[position.m_segment].m_endNode; - } - - if (Options.timedLightsEnabled) { - // when a TTL is active only reserve space if it shows green - PathUnit.Position nextPos; - if (pathMan.m_pathUnits.m_buffer[pathUnitId].GetNextPosition(pathPosIndex, out nextPos)) { - if (!VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, position, nextPos)) { - stopLoop = true; - } - } - } - // NON-STOCK CODE END - - if (reserveSpace && i >= 1 && i <= 2) { - uint laneID = PathManager.GetLaneID(position); - if (laneID != 0u) { - reserveSpace = netMan.m_lanes.m_buffer[laneID].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID); - } - } - ForceTrafficLights(transitNodeId, position); // NON-STOCK CODE - - // NON-STOCK CODE START - if (stopLoop) { - return; - } - // NON-STOCK CODE END - - if ((pathPosIndex += 1) >= pathMan.m_pathUnits.m_buffer[pathUnitId].m_positionCount) { - pathUnitId = pathMan.m_pathUnits.m_buffer[pathUnitId].m_nextPathUnit; - pathPosIndex = 0; - if (pathUnitId == 0u) { - return; - } - } - } - } - } - - // slightly modified version of TrainAI.ForceTrafficLights(PathUnit.Position) - private static void ForceTrafficLights(ushort transitNodeId, PathUnit.Position position) { - NetManager netMan = Singleton.instance; - if ((netMan.m_nodes.m_buffer[(int)transitNodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None) { - uint frame = Singleton.instance.m_currentFrameIndex; - uint simGroup = (uint)transitNodeId >> 7; - uint rand = frame - simGroup & 255u; - RoadBaseAI.TrafficLightState vehicleLightState; - RoadBaseAI.TrafficLightState pedestrianLightState; - bool vehicles; - bool pedestrians; - RoadBaseAI.GetTrafficLightState(transitNodeId, ref netMan.m_segments.m_buffer[(int)position.m_segment], frame - simGroup, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); - if (!vehicles && rand >= 196u) { - vehicles = true; - RoadBaseAI.SetTrafficLightState(transitNodeId, ref netMan.m_segments.m_buffer[(int)position.m_segment], frame - simGroup, vehicleLightState, pedestrianLightState, vehicles, pedestrians); - } - } - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - protected static bool CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle) { - Log.Error("CustomTrainAI.CheckOverlap (1) called."); - return false; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - protected static ushort CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle, ushort otherID, ref Vehicle otherData, ref bool overlap, Vector3 min, Vector3 max) { - Log.Error("CustomTrainAI.CheckOverlap (2) called."); - return 0; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { - Log.Error("CustomTrainAI.InitializePath called"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - public static void UpdatePathTargetPositions(TrainAI trainAI, ushort vehicleID, ref Vehicle vehicleData, Vector3 refPos1, Vector3 refPos2, ushort leaderID, ref Vehicle leaderData, ref int index, int max1, int max2, float minSqrDistanceA, float minSqrDistanceB) { - Log.Error($"CustomTrainAI.InvokeUpdatePathTargetPositions called! trainAI={trainAI}"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Reverse(ushort leaderID, ref Vehicle leaderData) { - Log.Error("CustomTrainAI.Reverse called"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { - Log.Error("CustomTrainAI.GetMaxSpeed called"); - return 0f; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { - Log.Error("CustomTrainAI.CalculateMaxSpeed called"); - return 0f; - } - } + maxSpeed = 0; + return; + } else { + ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); + } + maxSpeed = oldMaxSpeed; + } + //} // NON-STOCK CODE + } + } + + [RedirectMethod] + private void CustomForceTrafficLights(ushort vehicleID, ref Vehicle vehicleData, bool reserveSpace) { + uint pathUnitId = vehicleData.m_path; + if (pathUnitId != 0u) { + NetManager netMan = Singleton.instance; + PathManager pathMan = Singleton.instance; + byte pathPosIndex = vehicleData.m_pathPositionIndex; + if (pathPosIndex == 255) { + pathPosIndex = 0; + } + pathPosIndex = (byte)(pathPosIndex >> 1); + bool stopLoop = false; // NON-STOCK CODE + for (int i = 0; i < 6; i++) { + PathUnit.Position position; + if (!pathMan.m_pathUnits.m_buffer[pathUnitId].GetPosition(pathPosIndex, out position)) { + return; + } + + // NON-STOCK CODE START + ushort transitNodeId; + if (position.m_offset < 128) { + transitNodeId = netMan.m_segments.m_buffer[position.m_segment].m_startNode; + } else { + transitNodeId = netMan.m_segments.m_buffer[position.m_segment].m_endNode; + } + + if (Options.timedLightsEnabled) { + // when a TTL is active only reserve space if it shows green + PathUnit.Position nextPos; + if (pathMan.m_pathUnits.m_buffer[pathUnitId].GetNextPosition(pathPosIndex, out nextPos)) { + if (!VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, position, nextPos)) { + stopLoop = true; + } + } + } + // NON-STOCK CODE END + + if (reserveSpace && i >= 1 && i <= 2) { + uint laneID = PathManager.GetLaneID(position); + if (laneID != 0u) { + reserveSpace = netMan.m_lanes.m_buffer[laneID].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID); + } + } + ForceTrafficLights(transitNodeId, position); // NON-STOCK CODE + + // NON-STOCK CODE START + if (stopLoop) { + return; + } + // NON-STOCK CODE END + + if ((pathPosIndex += 1) >= pathMan.m_pathUnits.m_buffer[pathUnitId].m_positionCount) { + pathUnitId = pathMan.m_pathUnits.m_buffer[pathUnitId].m_nextPathUnit; + pathPosIndex = 0; + if (pathUnitId == 0u) { + return; + } + } + } + } + } + + // slightly modified version of TrainAI.ForceTrafficLights(PathUnit.Position) + private static void ForceTrafficLights(ushort transitNodeId, PathUnit.Position position) { + NetManager netMan = Singleton.instance; + if ((netMan.m_nodes.m_buffer[(int)transitNodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None) { + uint frame = Singleton.instance.m_currentFrameIndex; + uint simGroup = (uint)transitNodeId >> 7; + uint rand = frame - simGroup & 255u; + RoadBaseAI.TrafficLightState vehicleLightState; + RoadBaseAI.TrafficLightState pedestrianLightState; + bool vehicles; + bool pedestrians; + RoadBaseAI.GetTrafficLightState(transitNodeId, ref netMan.m_segments.m_buffer[(int)position.m_segment], frame - simGroup, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); + if (!vehicles && rand >= 196u) { + vehicles = true; + RoadBaseAI.SetTrafficLightState(transitNodeId, ref netMan.m_segments.m_buffer[(int)position.m_segment], frame - simGroup, vehicleLightState, pedestrianLightState, vehicles, pedestrians); + } + } + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + protected static bool CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle) { + Log.Error("CustomTrainAI.CheckOverlap (1) called."); + return false; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + protected static ushort CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle, ushort otherID, ref Vehicle otherData, ref bool overlap, Vector3 min, Vector3 max) { + Log.Error("CustomTrainAI.CheckOverlap (2) called."); + return 0; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { + Log.Error("CustomTrainAI.InitializePath called"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void UpdatePathTargetPositions(TrainAI trainAI, ushort vehicleID, ref Vehicle vehicleData, Vector3 refPos1, Vector3 refPos2, ushort leaderID, ref Vehicle leaderData, ref int index, int max1, int max2, float minSqrDistanceA, float minSqrDistanceB) { + Log.Error($"CustomTrainAI.InvokeUpdatePathTargetPositions called! trainAI={trainAI}"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Reverse(ushort leaderID, ref Vehicle leaderData) { + Log.Error("CustomTrainAI.Reverse called"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { + Log.Error("CustomTrainAI.GetMaxSpeed called"); + return 0f; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { + Log.Error("CustomTrainAI.CalculateMaxSpeed called"); + return 0f; + } + } } \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTramBaseAI.cs b/TLM/TLM/Custom/AI/CustomTramBaseAI.cs index 030408db2..06be41a72 100644 --- a/TLM/TLM/Custom/AI/CustomTramBaseAI.cs +++ b/TLM/TLM/Custom/AI/CustomTramBaseAI.cs @@ -1,774 +1,774 @@ -using ColossalFramework; -using ColossalFramework.Math; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.State; -using TrafficManager.Geometry; -using UnityEngine; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using CSUtil.Commons; -using System.Runtime.CompilerServices; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic.Data; -using CSUtil.Commons.Benchmark; -using static TrafficManager.Custom.PathFinding.CustomPathManager; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TramBaseAI))] - public class CustomTramBaseAI : TramBaseAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - - if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) { - byte pathFindFlags = Singleton.instance.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags; - - if ((pathFindFlags & PathUnit.FLAG_READY) != 0) { - try { - this.PathfindSuccess(vehicleId, ref vehicleData); - this.PathFindReady(vehicleId, ref vehicleData); - } catch (Exception e) { - Log.Warning($"TramBaseAI.PathFindSuccess/PathFindReady({vehicleId}) threw an exception: {e.ToString()}"); - vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; - Singleton.instance.ReleasePath(vehicleData.m_path); - vehicleData.m_path = 0u; - this.PathfindFailure(vehicleId, ref vehicleData); - return; - } - } else if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) { - vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; - Singleton.instance.ReleasePath(vehicleData.m_path); - vehicleData.m_path = 0u; - this.PathfindFailure(vehicleId, ref vehicleData); - return; - } - } else { - if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) { - this.TrySpawn(vehicleId, ref vehicleData); - } - } - - // NON-STOCK CODE START - extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); - - if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { - // Advanced AI traffic measurement - extVehicleMan.LogTraffic(vehicleId, ref vehicleData); - } - // NON-STOCK CODE END - - VehicleManager instance = Singleton.instance; - VehicleInfo info = instance.m_vehicles.m_buffer[(int)vehicleId].Info; - info.m_vehicleAI.SimulationStep(vehicleId, ref instance.m_vehicles.m_buffer[(int)vehicleId], vehicleId, ref vehicleData, 0); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { - return; - } - ushort trailingVehicle = instance.m_vehicles.m_buffer[(int)vehicleId].m_trailingVehicle; - int num = 0; - while (trailingVehicle != 0) { - info = instance.m_vehicles.m_buffer[(int)trailingVehicle].Info; - info.m_vehicleAI.SimulationStep(trailingVehicle, ref instance.m_vehicles.m_buffer[(int)trailingVehicle], vehicleId, ref vehicleData, 0); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { - return; - } - trailingVehicle = instance.m_vehicles.m_buffer[(int)trailingVehicle].m_trailingVehicle; - if (++num > 16384) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace | Vehicle.Flags.WaitingCargo)) == 0) { - Singleton.instance.ReleaseVehicle(vehicleId); - } else if (vehicleData.m_blockCounter == 255) { - // NON-STOCK CODE START - if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { - // NON-STOCK CODE END - Singleton.instance.ReleaseVehicle(vehicleId); - } // NON-STOCK CODE - } - } - - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { - ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); // NON-STOCK CODE - - VehicleInfo info = this.m_info; - bool allowUnderground; - bool allowUnderground2; - if (info.m_vehicleType == VehicleInfo.VehicleType.Metro) { - allowUnderground = true; - allowUnderground2 = true; - } else { - allowUnderground = ((vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0); - allowUnderground2 = false; - } - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startSqrDistA; - float startSqrDistB; - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endSqrDistA; - float endSqrDistB; - if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && - CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle, info.m_vehicleType, allowUnderground2, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { - if (!startBothWays || startSqrDistB > startSqrDistA * 1.2f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endSqrDistB > endSqrDistA * 1.2f) { - endPosB = default(PathUnit.Position); - } - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = ExtVehicleType.Tram; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.laneTypes = NetInfo.LaneType.Vehicle; - args.vehicleTypes = info.m_vehicleType; - args.maxLength = 20000f; - args.isHeavyVehicle = false; - args.hasCombustionEngine = false; - args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = true; - args.skipQueue = true; - - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextPosition, PathUnit.Position prevPosition, uint prevLaneId, byte prevOffset, PathUnit.Position refPosition, uint refLaneId, byte refOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) { - NetManager netManager = Singleton.instance; - ushort prevSourceNodeId; - ushort prevTargetNodeId; - if (prevOffset < prevPosition.m_offset) { - prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode; - prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode; - } else { - prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode; - prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode; - } - - ushort refTargetNodeId; - if (refOffset == 0) { - refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_startNode; - } else { - refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_endNode; - } +namespace TrafficManager.Custom.AI { + using System; + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using UnityEngine; + + [TargetType(typeof(TramBaseAI))] + public class CustomTramBaseAI : TramBaseAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + + if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) { + byte pathFindFlags = Singleton.instance.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags; + + if ((pathFindFlags & PathUnit.FLAG_READY) != 0) { + try { + this.PathfindSuccess(vehicleId, ref vehicleData); + this.PathFindReady(vehicleId, ref vehicleData); + } catch (Exception e) { + Log.Warning($"TramBaseAI.PathFindSuccess/PathFindReady({vehicleId}) threw an exception: {e.ToString()}"); + vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; + Singleton.instance.ReleasePath(vehicleData.m_path); + vehicleData.m_path = 0u; + this.PathfindFailure(vehicleId, ref vehicleData); + return; + } + } else if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) { + vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath; + Singleton.instance.ReleasePath(vehicleData.m_path); + vehicleData.m_path = 0u; + this.PathfindFailure(vehicleId, ref vehicleData); + return; + } + } else { + if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) { + this.TrySpawn(vehicleId, ref vehicleData); + } + } + + // NON-STOCK CODE START + extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); + + if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { + // Advanced AI traffic measurement + extVehicleMan.LogTraffic(vehicleId, ref vehicleData); + } + // NON-STOCK CODE END + + VehicleManager instance = Singleton.instance; + VehicleInfo info = instance.m_vehicles.m_buffer[(int)vehicleId].Info; + info.m_vehicleAI.SimulationStep(vehicleId, ref instance.m_vehicles.m_buffer[(int)vehicleId], vehicleId, ref vehicleData, 0); + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { + return; + } + ushort trailingVehicle = instance.m_vehicles.m_buffer[(int)vehicleId].m_trailingVehicle; + int num = 0; + while (trailingVehicle != 0) { + info = instance.m_vehicles.m_buffer[(int)trailingVehicle].Info; + info.m_vehicleAI.SimulationStep(trailingVehicle, ref instance.m_vehicles.m_buffer[(int)trailingVehicle], vehicleId, ref vehicleData, 0); + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { + return; + } + trailingVehicle = instance.m_vehicles.m_buffer[(int)trailingVehicle].m_trailingVehicle; + if (++num > 16384) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace | Vehicle.Flags.WaitingCargo)) == 0) { + Singleton.instance.ReleaseVehicle(vehicleId); + } else if (vehicleData.m_blockCounter == 255) { + // NON-STOCK CODE START + if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { + // NON-STOCK CODE END + Singleton.instance.ReleaseVehicle(vehicleId); + } // NON-STOCK CODE + } + } + + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { + ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); // NON-STOCK CODE + + VehicleInfo info = this.m_info; + bool allowUnderground; + bool allowUnderground2; + if (info.m_vehicleType == VehicleInfo.VehicleType.Metro) { + allowUnderground = true; + allowUnderground2 = true; + } else { + allowUnderground = ((vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0); + allowUnderground2 = false; + } + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startSqrDistA; + float startSqrDistB; + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endSqrDistA; + float endSqrDistB; + if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) && + CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle, info.m_vehicleType, allowUnderground2, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { + if (!startBothWays || startSqrDistB > startSqrDistA * 1.2f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endSqrDistB > endSqrDistA * 1.2f) { + endPosB = default(PathUnit.Position); + } + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = ExtVehicleType.Tram; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.laneTypes = NetInfo.LaneType.Vehicle; + args.vehicleTypes = info.m_vehicleType; + args.maxLength = 20000f; + args.isHeavyVehicle = false; + args.hasCombustionEngine = false; + args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData); + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = true; + args.skipQueue = true; + + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextPosition, PathUnit.Position prevPosition, uint prevLaneId, byte prevOffset, PathUnit.Position refPosition, uint refLaneId, byte refOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) { + NetManager netManager = Singleton.instance; + ushort prevSourceNodeId; + ushort prevTargetNodeId; + if (prevOffset < prevPosition.m_offset) { + prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode; + prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode; + } else { + prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode; + prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode; + } + + ushort refTargetNodeId; + if (refOffset == 0) { + refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_startNode; + } else { + refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_endNode; + } #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[21] && (GlobalConfig.Instance.Debug.NodeId <= 0 || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) && (GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.None || GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.Tram) && (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId); - - if (debug) { - Log._Debug($"CustomTramBaseAI.CustomCalculateSegmentPosition({vehicleId}) called.\n" + - $"\trefPosition.m_segment={refPosition.m_segment}, refPosition.m_offset={refPosition.m_offset}\n" + - $"\tprevPosition.m_segment={prevPosition.m_segment}, prevPosition.m_offset={prevPosition.m_offset}\n" + - $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + - $"\trefLaneId={refLaneId}, refOffset={refOffset}\n" + - $"\tprevLaneId={prevLaneId}, prevOffset={prevOffset}\n" + - $"\tprevSourceNodeId={prevSourceNodeId}, prevTargetNodeId={prevTargetNodeId}\n" + - $"\trefTargetNodeId={refTargetNodeId}, refTargetNodeId={refTargetNodeId}\n" + - $"\tindex={index}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[21] + && (GlobalConfig.Instance.Debug.NodeId <= 0 + || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) + && (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.Tram) + && (GlobalConfig.Instance.Debug.VehicleId == 0 + || GlobalConfig.Instance.Debug.VehicleId == vehicleId); + + if (debug) { + Log._Debug($"CustomTramBaseAI.CustomCalculateSegmentPosition({vehicleId}) called.\n" + + $"\trefPosition.m_segment={refPosition.m_segment}, refPosition.m_offset={refPosition.m_offset}\n" + + $"\tprevPosition.m_segment={prevPosition.m_segment}, prevPosition.m_offset={prevPosition.m_offset}\n" + + $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + + $"\trefLaneId={refLaneId}, refOffset={refOffset}\n" + + $"\tprevLaneId={prevLaneId}, prevOffset={prevOffset}\n" + + $"\tprevSourceNodeId={prevSourceNodeId}, prevTargetNodeId={prevTargetNodeId}\n" + + $"\trefTargetNodeId={refTargetNodeId}, refTargetNodeId={refTargetNodeId}\n" + + $"\tindex={index}"); + } #endif - Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); - float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; - - netManager.m_lanes.m_buffer[prevLaneId].CalculatePositionAndDirection((float)prevOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); - Vector3 b = netManager.m_lanes.m_buffer[refLaneId].CalculatePosition((float)refOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - Vector3 a = lastFrameData.m_position; - Vector3 a2 = lastFrameData.m_position; - Vector3 b2 = lastFrameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); - a += b2; - a2 -= b2; - float crazyValue = 0.5f * sqrVelocity / this.m_info.m_braking; - float a3 = Vector3.Distance(a, b); - float b3 = Vector3.Distance(a2, b); - if (Mathf.Min(a3, b3) >= crazyValue - 1f) { - // dead stock code - /*Segment3 segment; - segment.a = pos; - if (prevOffset < prevPosition.m_offset) { - segment.b = pos + dir.normalized * this.m_info.m_generatedInfo.m_size.z; - } else { - segment.b = pos - dir.normalized * this.m_info.m_generatedInfo.m_size.z; - }*/ - if (prevSourceNodeId == refTargetNodeId) { - if (!VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref refPosition, ref netManager.m_segments.m_buffer[refPosition.m_segment], refTargetNodeId, refLaneId, ref prevPosition, prevSourceNodeId, ref netManager.m_nodes.m_buffer[prevSourceNodeId], prevLaneId, ref nextPosition, prevTargetNodeId)) { - maxSpeed = 0; - return; - } else { - ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); - } - } - } - - NetInfo info = netManager.m_segments.m_buffer[(int)prevPosition.m_segment].Info; - if (info.m_lanes != null && info.m_lanes.Length > (int)prevPosition.m_lane) { - float speedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(prevPosition.m_segment, prevPosition.m_lane, prevLaneId, info.m_lanes[prevPosition.m_lane]) : info.m_lanes[prevPosition.m_lane].m_speedLimit; // NON-STOCK CODE - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, speedLimit, netManager.m_lanes.m_buffer[prevLaneId].m_curve); - } else { - maxSpeed = this.CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); - } - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) { - NetManager instance = Singleton.instance; - instance.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); - NetInfo info = instance.m_segments.m_buffer[(int)position.m_segment].Info; - if (info.m_lanes != null && info.m_lanes.Length > (int)position.m_lane) { - float speedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info.m_lanes[position.m_lane]) : info.m_lanes[position.m_lane].m_speedLimit; // NON-STOCK CODE - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, speedLimit, instance.m_lanes.m_buffer[laneID].m_curve); - } else { - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); - } - } - - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ushort leaderID, ref Vehicle leaderData, int lodPhysics) { + Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); + float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; + + netManager.m_lanes.m_buffer[prevLaneId].CalculatePositionAndDirection((float)prevOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); + Vector3 b = netManager.m_lanes.m_buffer[refLaneId].CalculatePosition((float)refOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + Vector3 a = lastFrameData.m_position; + Vector3 a2 = lastFrameData.m_position; + Vector3 b2 = lastFrameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); + a += b2; + a2 -= b2; + float crazyValue = 0.5f * sqrVelocity / this.m_info.m_braking; + float a3 = Vector3.Distance(a, b); + float b3 = Vector3.Distance(a2, b); + if (Mathf.Min(a3, b3) >= crazyValue - 1f) { + // dead stock code + /*Segment3 segment; + segment.a = pos; + if (prevOffset < prevPosition.m_offset) { + segment.b = pos + dir.normalized * this.m_info.m_generatedInfo.m_size.z; + } else { + segment.b = pos - dir.normalized * this.m_info.m_generatedInfo.m_size.z; + }*/ + if (prevSourceNodeId == refTargetNodeId) { + if (!VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref refPosition, ref netManager.m_segments.m_buffer[refPosition.m_segment], refTargetNodeId, refLaneId, ref prevPosition, prevSourceNodeId, ref netManager.m_nodes.m_buffer[prevSourceNodeId], prevLaneId, ref nextPosition, prevTargetNodeId)) { + maxSpeed = 0; + return; + } else { + ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); + } + } + } + + NetInfo info = netManager.m_segments.m_buffer[(int)prevPosition.m_segment].Info; + if (info.m_lanes != null && info.m_lanes.Length > (int)prevPosition.m_lane) { + float speedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(prevPosition.m_segment, prevPosition.m_lane, prevLaneId, info.m_lanes[prevPosition.m_lane]) : info.m_lanes[prevPosition.m_lane].m_speedLimit; // NON-STOCK CODE + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, speedLimit, netManager.m_lanes.m_buffer[prevLaneId].m_curve); + } else { + maxSpeed = this.CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); + } + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) { + NetManager instance = Singleton.instance; + instance.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); + NetInfo info = instance.m_segments.m_buffer[(int)position.m_segment].Info; + if (info.m_lanes != null && info.m_lanes.Length > (int)position.m_lane) { + float speedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info.m_lanes[position.m_lane]) : info.m_lanes[position.m_lane].m_speedLimit; // NON-STOCK CODE + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, speedLimit, instance.m_lanes.m_buffer[laneID].m_curve); + } else { + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); + } + } + + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ushort leaderID, ref Vehicle leaderData, int lodPhysics) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[16] && GlobalConfig.Instance.Debug.NodeId == vehicleID; + bool debug = GlobalConfig.Instance.Debug.Switches[16] && GlobalConfig.Instance.Debug.NodeId == vehicleID; #endif - ushort leadingVehicle = vehicleData.m_leadingVehicle; - uint currentFrameIndex = Singleton.instance.m_currentFrameIndex; - VehicleInfo leaderInfo; - if (leaderID != vehicleID) { - leaderInfo = leaderData.Info; - } else { - leaderInfo = this.m_info; - } - TramBaseAI tramBaseAI = leaderInfo.m_vehicleAI as TramBaseAI; - - if (leadingVehicle != 0) { - frameData.m_position += frameData.m_velocity * 0.4f; - } else { - frameData.m_position += frameData.m_velocity * 0.5f; - } - - frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; - Vector3 wheelBaseRot = frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); - Vector3 posAfterWheelRot = frameData.m_position + wheelBaseRot; - Vector3 posBeforeWheelRot = frameData.m_position - wheelBaseRot; - - float acceleration = this.m_info.m_acceleration; - float braking = this.m_info.m_braking; - float curSpeed = frameData.m_velocity.magnitude; - - Vector3 afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot; - float afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude; - - Quaternion curInvRot = Quaternion.Inverse(frameData.m_rotation); - Vector3 curveTangent = curInvRot * frameData.m_velocity; + ushort leadingVehicle = vehicleData.m_leadingVehicle; + uint currentFrameIndex = Singleton.instance.m_currentFrameIndex; + VehicleInfo leaderInfo; + if (leaderID != vehicleID) { + leaderInfo = leaderData.Info; + } else { + leaderInfo = this.m_info; + } + TramBaseAI tramBaseAI = leaderInfo.m_vehicleAI as TramBaseAI; + + if (leadingVehicle != 0) { + frameData.m_position += frameData.m_velocity * 0.4f; + } else { + frameData.m_position += frameData.m_velocity * 0.5f; + } + + frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; + Vector3 wheelBaseRot = frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); + Vector3 posAfterWheelRot = frameData.m_position + wheelBaseRot; + Vector3 posBeforeWheelRot = frameData.m_position - wheelBaseRot; + + float acceleration = this.m_info.m_acceleration; + float braking = this.m_info.m_braking; + float curSpeed = frameData.m_velocity.magnitude; + + Vector3 afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot; + float afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude; + + Quaternion curInvRot = Quaternion.Inverse(frameData.m_rotation); + Vector3 curveTangent = curInvRot * frameData.m_velocity; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): ================================================"); - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): leadingVehicle={leadingVehicle} frameData.m_position={frameData.m_position} frameData.m_swayPosition={frameData.m_swayPosition} wheelBaseRot={wheelBaseRot} posAfterWheelRot={posAfterWheelRot} posBeforeWheelRot={posBeforeWheelRot} acceleration={acceleration} braking={braking} curSpeed={curSpeed} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag} curInvRot={curInvRot} curveTangent={curveTangent} this.m_info.m_generatedInfo.m_wheelBase={this.m_info.m_generatedInfo.m_wheelBase}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): ================================================"); + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): leadingVehicle={leadingVehicle} frameData.m_position={frameData.m_position} frameData.m_swayPosition={frameData.m_swayPosition} wheelBaseRot={wheelBaseRot} posAfterWheelRot={posAfterWheelRot} posBeforeWheelRot={posBeforeWheelRot} acceleration={acceleration} braking={braking} curSpeed={curSpeed} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag} curInvRot={curInvRot} curveTangent={curveTangent} this.m_info.m_generatedInfo.m_wheelBase={this.m_info.m_generatedInfo.m_wheelBase}"); + } #endif - Vector3 forward = Vector3.forward; - Vector3 targetMotion = Vector3.zero; - float targetSpeed = 0f; - float motionFactor = 0.5f; - float turnAngle = 0f; - if (leadingVehicle != 0) { - VehicleManager vehMan = Singleton.instance; - Vehicle.Frame leadingVehLastFrameData = vehMan.m_vehicles.m_buffer[(int)leadingVehicle].GetLastFrameData(); - VehicleInfo leadingVehInfo = vehMan.m_vehicles.m_buffer[(int)leadingVehicle].Info; - - float attachOffset; - if ((vehicleData.m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0) { - attachOffset = this.m_info.m_attachOffsetBack - this.m_info.m_generatedInfo.m_size.z * 0.5f; - } else { - attachOffset = this.m_info.m_attachOffsetFront - this.m_info.m_generatedInfo.m_size.z * 0.5f; - } - - float leadingAttachOffset; - if ((vehMan.m_vehicles.m_buffer[(int)leadingVehicle].m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0) { - leadingAttachOffset = leadingVehInfo.m_attachOffsetFront - leadingVehInfo.m_generatedInfo.m_size.z * 0.5f; - } else { - leadingAttachOffset = leadingVehInfo.m_attachOffsetBack - leadingVehInfo.m_generatedInfo.m_size.z * 0.5f; - } - - Vector3 curPosMinusRotAttachOffset = frameData.m_position - frameData.m_rotation * new Vector3(0f, 0f, attachOffset); - Vector3 leadingPosPlusRotAttachOffset = leadingVehLastFrameData.m_position + leadingVehLastFrameData.m_rotation * new Vector3(0f, 0f, leadingAttachOffset); - - wheelBaseRot = leadingVehLastFrameData.m_rotation * new Vector3(0f, 0f, leadingVehInfo.m_generatedInfo.m_wheelBase * 0.5f); - Vector3 leadingPosBeforeWheelRot = leadingVehLastFrameData.m_position - wheelBaseRot; - - if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - int someIndex = -1; - UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, 0, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); - afterRotToTargetPos1DiffSqrMag = 0f; - } - - float attachRotDist = Mathf.Max(Vector3.Distance(curPosMinusRotAttachOffset, leadingPosPlusRotAttachOffset), 2f); - - float one = 1f; - float attachRotSqrDist = attachRotDist * attachRotDist; - float oneSqr = one * one; - int i = 0; - if (afterRotToTargetPos1DiffSqrMag < attachRotSqrDist) { - if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, posBeforeWheelRot, posAfterWheelRot, 0, ref leaderData, ref i, 1, 2, attachRotSqrDist, oneSqr); - } - while (i < 4) { - vehicleData.SetTargetPos(i, vehicleData.GetTargetPos(i - 1)); - i++; - } - afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot; - afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude; - } - afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff; - - float negTotalAttachLen = -((this.m_info.m_generatedInfo.m_wheelBase + leadingVehInfo.m_generatedInfo.m_wheelBase) * 0.5f + attachOffset + leadingAttachOffset); - bool hasPath = false; - if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - float u1; - float u2; - if (Line3.Intersect(posAfterWheelRot, vehicleData.m_targetPos1, leadingPosBeforeWheelRot, negTotalAttachLen, out u1, out u2)) { - targetMotion = afterRotToTargetPos1Diff * Mathf.Clamp(Mathf.Min(u1, u2) / 0.6f, 0f, 2f); - } else { - Line3.DistanceSqr(posAfterWheelRot, vehicleData.m_targetPos1, leadingPosBeforeWheelRot, out u1); - targetMotion = afterRotToTargetPos1Diff * Mathf.Clamp(u1 / 0.6f, 0f, 2f); - } - hasPath = true; - } - - if (hasPath) { - if (Vector3.Dot(leadingPosBeforeWheelRot - posAfterWheelRot, posAfterWheelRot - posBeforeWheelRot) < 0f) { - motionFactor = 0f; - } - } else { - float leadingPosBeforeToAfterWheelRotDist = Vector3.Distance(leadingPosBeforeWheelRot, posAfterWheelRot); - motionFactor = 0f; - targetMotion = curInvRot * ((leadingPosBeforeWheelRot - posAfterWheelRot) * (Mathf.Max(0f, leadingPosBeforeToAfterWheelRotDist - negTotalAttachLen) / Mathf.Max(1f, leadingPosBeforeToAfterWheelRotDist * 0.6f))); - } - } else { - float estimatedFrameDist = (curSpeed + acceleration) * (0.5f + 0.5f * (curSpeed + acceleration) / braking) + (this.m_info.m_generatedInfo.m_size.z - this.m_info.m_generatedInfo.m_wheelBase) * 0.5f; - float maxSpeedAdd = Mathf.Max(curSpeed + acceleration, 2f); - float meanSpeedAdd = Mathf.Max((estimatedFrameDist - maxSpeedAdd) / 2f, 2f); - float maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; - float meanSpeedAddSqr = meanSpeedAdd * meanSpeedAdd; - if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { - int someIndex = -1; - UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, leaderID, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); - afterRotToTargetPos1DiffSqrMag = 0f; + Vector3 forward = Vector3.forward; + Vector3 targetMotion = Vector3.zero; + float targetSpeed = 0f; + float motionFactor = 0.5f; + float turnAngle = 0f; + if (leadingVehicle != 0) { + VehicleManager vehMan = Singleton.instance; + Vehicle.Frame leadingVehLastFrameData = vehMan.m_vehicles.m_buffer[(int)leadingVehicle].GetLastFrameData(); + VehicleInfo leadingVehInfo = vehMan.m_vehicles.m_buffer[(int)leadingVehicle].Info; + + float attachOffset; + if ((vehicleData.m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0) { + attachOffset = this.m_info.m_attachOffsetBack - this.m_info.m_generatedInfo.m_size.z * 0.5f; + } else { + attachOffset = this.m_info.m_attachOffsetFront - this.m_info.m_generatedInfo.m_size.z * 0.5f; + } + + float leadingAttachOffset; + if ((vehMan.m_vehicles.m_buffer[(int)leadingVehicle].m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0) { + leadingAttachOffset = leadingVehInfo.m_attachOffsetFront - leadingVehInfo.m_generatedInfo.m_size.z * 0.5f; + } else { + leadingAttachOffset = leadingVehInfo.m_attachOffsetBack - leadingVehInfo.m_generatedInfo.m_size.z * 0.5f; + } + + Vector3 curPosMinusRotAttachOffset = frameData.m_position - frameData.m_rotation * new Vector3(0f, 0f, attachOffset); + Vector3 leadingPosPlusRotAttachOffset = leadingVehLastFrameData.m_position + leadingVehLastFrameData.m_rotation * new Vector3(0f, 0f, leadingAttachOffset); + + wheelBaseRot = leadingVehLastFrameData.m_rotation * new Vector3(0f, 0f, leadingVehInfo.m_generatedInfo.m_wheelBase * 0.5f); + Vector3 leadingPosBeforeWheelRot = leadingVehLastFrameData.m_position - wheelBaseRot; + + if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + int someIndex = -1; + UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, 0, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); + afterRotToTargetPos1DiffSqrMag = 0f; + } + + float attachRotDist = Mathf.Max(Vector3.Distance(curPosMinusRotAttachOffset, leadingPosPlusRotAttachOffset), 2f); + + float one = 1f; + float attachRotSqrDist = attachRotDist * attachRotDist; + float oneSqr = one * one; + int i = 0; + if (afterRotToTargetPos1DiffSqrMag < attachRotSqrDist) { + if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, posBeforeWheelRot, posAfterWheelRot, 0, ref leaderData, ref i, 1, 2, attachRotSqrDist, oneSqr); + } + while (i < 4) { + vehicleData.SetTargetPos(i, vehicleData.GetTargetPos(i - 1)); + i++; + } + afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot; + afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude; + } + afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff; + + float negTotalAttachLen = -((this.m_info.m_generatedInfo.m_wheelBase + leadingVehInfo.m_generatedInfo.m_wheelBase) * 0.5f + attachOffset + leadingAttachOffset); + bool hasPath = false; + if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + float u1; + float u2; + if (Line3.Intersect(posAfterWheelRot, vehicleData.m_targetPos1, leadingPosBeforeWheelRot, negTotalAttachLen, out u1, out u2)) { + targetMotion = afterRotToTargetPos1Diff * Mathf.Clamp(Mathf.Min(u1, u2) / 0.6f, 0f, 2f); + } else { + Line3.DistanceSqr(posAfterWheelRot, vehicleData.m_targetPos1, leadingPosBeforeWheelRot, out u1); + targetMotion = afterRotToTargetPos1Diff * Mathf.Clamp(u1 / 0.6f, 0f, 2f); + } + hasPath = true; + } + + if (hasPath) { + if (Vector3.Dot(leadingPosBeforeWheelRot - posAfterWheelRot, posAfterWheelRot - posBeforeWheelRot) < 0f) { + motionFactor = 0f; + } + } else { + float leadingPosBeforeToAfterWheelRotDist = Vector3.Distance(leadingPosBeforeWheelRot, posAfterWheelRot); + motionFactor = 0f; + targetMotion = curInvRot * ((leadingPosBeforeWheelRot - posAfterWheelRot) * (Mathf.Max(0f, leadingPosBeforeToAfterWheelRotDist - negTotalAttachLen) / Mathf.Max(1f, leadingPosBeforeToAfterWheelRotDist * 0.6f))); + } + } else { + float estimatedFrameDist = (curSpeed + acceleration) * (0.5f + 0.5f * (curSpeed + acceleration) / braking) + (this.m_info.m_generatedInfo.m_size.z - this.m_info.m_generatedInfo.m_wheelBase) * 0.5f; + float maxSpeedAdd = Mathf.Max(curSpeed + acceleration, 2f); + float meanSpeedAdd = Mathf.Max((estimatedFrameDist - maxSpeedAdd) / 2f, 2f); + float maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; + float meanSpeedAddSqr = meanSpeedAdd * meanSpeedAdd; + if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { + int someIndex = -1; + UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, leaderID, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); + afterRotToTargetPos1DiffSqrMag = 0f; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): dot < 0"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): dot < 0"); + } #endif - } + } #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Leading vehicle is 0. vehicleData.m_targetPos0={vehicleData.m_targetPos0} vehicleData.m_targetPos1={vehicleData.m_targetPos1} posBeforeWheelRot={posBeforeWheelRot} posBeforeWheelRot={posAfterWheelRot} estimatedFrameDist={estimatedFrameDist} maxSpeedAdd={maxSpeedAdd} meanSpeedAdd={meanSpeedAdd} maxSpeedAddSqr={maxSpeedAddSqr} meanSpeedAddSqr={meanSpeedAddSqr} afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Leading vehicle is 0. vehicleData.m_targetPos0={vehicleData.m_targetPos0} vehicleData.m_targetPos1={vehicleData.m_targetPos1} posBeforeWheelRot={posBeforeWheelRot} posBeforeWheelRot={posAfterWheelRot} estimatedFrameDist={estimatedFrameDist} maxSpeedAdd={maxSpeedAdd} meanSpeedAdd={meanSpeedAdd} maxSpeedAddSqr={maxSpeedAddSqr} meanSpeedAddSqr={meanSpeedAddSqr} afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag}"); + } #endif - int posIndex = 0; - bool hasValidPathTargetPos = false; - if ((afterRotToTargetPos1DiffSqrMag < maxSpeedAddSqr || vehicleData.m_targetPos3.w < 0.01f) && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { - if (vehicleData.m_path != 0u) { - UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, posBeforeWheelRot, posAfterWheelRot, leaderID, ref leaderData, ref posIndex, 1, 4, maxSpeedAddSqr, meanSpeedAddSqr); - } - if (posIndex < 4) { - hasValidPathTargetPos = true; - while (posIndex < 4) { - vehicleData.SetTargetPos(posIndex, vehicleData.GetTargetPos(posIndex - 1)); - posIndex++; - } - } - afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot; - afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude; - } + int posIndex = 0; + bool hasValidPathTargetPos = false; + if ((afterRotToTargetPos1DiffSqrMag < maxSpeedAddSqr || vehicleData.m_targetPos3.w < 0.01f) && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) { + if (vehicleData.m_path != 0u) { + UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, posBeforeWheelRot, posAfterWheelRot, leaderID, ref leaderData, ref posIndex, 1, 4, maxSpeedAddSqr, meanSpeedAddSqr); + } + if (posIndex < 4) { + hasValidPathTargetPos = true; + while (posIndex < 4) { + vehicleData.SetTargetPos(posIndex, vehicleData.GetTargetPos(posIndex - 1)); + posIndex++; + } + } + afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot; + afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude; + } #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): posIndex={posIndex} hasValidPathTargetPos={hasValidPathTargetPos}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): posIndex={posIndex} hasValidPathTargetPos={hasValidPathTargetPos}"); + } #endif - if (leaderData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - NetManager netMan = Singleton.instance; - byte leaderPathPosIndex = leaderData.m_pathPositionIndex; - byte leaderLastPathOffset = leaderData.m_lastPathOffset; - if (leaderPathPosIndex == 255) { - leaderPathPosIndex = 0; - } - int noise; - float leaderLen = 1f + leaderData.CalculateTotalLength(leaderID, out noise); + if (leaderData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + NetManager netMan = Singleton.instance; + byte leaderPathPosIndex = leaderData.m_pathPositionIndex; + byte leaderLastPathOffset = leaderData.m_lastPathOffset; + if (leaderPathPosIndex == 255) { + leaderPathPosIndex = 0; + } + int noise; + float leaderLen = 1f + leaderData.CalculateTotalLength(leaderID, out noise); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): leaderPathPosIndex={leaderPathPosIndex} leaderLastPathOffset={leaderLastPathOffset} leaderPathPosIndex={leaderPathPosIndex} leaderLen={leaderLen}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): leaderPathPosIndex={leaderPathPosIndex} leaderLastPathOffset={leaderLastPathOffset} leaderPathPosIndex={leaderPathPosIndex} leaderLen={leaderLen}"); + } #endif - // reserve space / add traffic - PathManager pathMan = Singleton.instance; - PathUnit.Position pathPos; - if (pathMan.m_pathUnits.m_buffer[leaderData.m_path].GetPosition(leaderPathPosIndex >> 1, out pathPos)) { - netMan.m_segments.m_buffer[(int)pathPos.m_segment].AddTraffic(Mathf.RoundToInt(leaderLen * 2.5f), noise); - bool reservedSpaceOnCurrentLane = false; - if ((leaderPathPosIndex & 1) == 0 || leaderLastPathOffset == 0) { - uint laneId = PathManager.GetLaneID(pathPos); - if (laneId != 0u) { - Vector3 curPathOffsetPos = netMan.m_lanes.m_buffer[laneId].CalculatePosition((float)pathPos.m_offset * 0.003921569f); - float speedAdd = 0.5f * curSpeed * curSpeed / this.m_info.m_braking; - float afterWheelRotCurPathOffsetDist = Vector3.Distance(posAfterWheelRot, curPathOffsetPos); - float beforeWheelRotCurPathOffsetDist = Vector3.Distance(posBeforeWheelRot, curPathOffsetPos); - if (Mathf.Min(afterWheelRotCurPathOffsetDist, beforeWheelRotCurPathOffsetDist) >= speedAdd - 1f) { - netMan.m_lanes.m_buffer[laneId].ReserveSpace(leaderLen); - reservedSpaceOnCurrentLane = true; - } - } - } - - if (!reservedSpaceOnCurrentLane && pathMan.m_pathUnits.m_buffer[leaderData.m_path].GetNextPosition(leaderPathPosIndex >> 1, out pathPos)) { - uint nextLaneId = PathManager.GetLaneID(pathPos); - if (nextLaneId != 0u) { - netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(leaderLen); - } - } - } - - if ((ulong)(currentFrameIndex >> 4 & 15u) == (ulong)((long)(leaderID & 15))) { - // check if vehicle can proceed to next path position - - bool canProceeed = false; - uint curLeaderPathId = leaderData.m_path; - int curLeaderPathPosIndex = leaderPathPosIndex >> 1; - int k = 0; - while (k < 5) { - bool invalidPos; - if (PathUnit.GetNextPosition(ref curLeaderPathId, ref curLeaderPathPosIndex, out pathPos, out invalidPos)) { - uint laneId = PathManager.GetLaneID(pathPos); - if (laneId != 0u && !netMan.m_lanes.m_buffer[laneId].CheckSpace(leaderLen)) { - k++; - continue; - } - } - if (invalidPos) { - this.InvalidPath(vehicleID, ref vehicleData, leaderID, ref leaderData); - } - canProceeed = true; - break; - } - if (!canProceeed) { - leaderData.m_flags |= Vehicle.Flags.Congestion; - } - } - } - - float maxSpeed; - if ((leaderData.m_flags & Vehicle.Flags.Stopped) != (Vehicle.Flags)0) { - maxSpeed = 0f; + // reserve space / add traffic + PathManager pathMan = Singleton.instance; + PathUnit.Position pathPos; + if (pathMan.m_pathUnits.m_buffer[leaderData.m_path].GetPosition(leaderPathPosIndex >> 1, out pathPos)) { + netMan.m_segments.m_buffer[(int)pathPos.m_segment].AddTraffic(Mathf.RoundToInt(leaderLen * 2.5f), noise); + bool reservedSpaceOnCurrentLane = false; + if ((leaderPathPosIndex & 1) == 0 || leaderLastPathOffset == 0) { + uint laneId = PathManager.GetLaneID(pathPos); + if (laneId != 0u) { + Vector3 curPathOffsetPos = netMan.m_lanes.m_buffer[laneId].CalculatePosition((float)pathPos.m_offset * 0.003921569f); + float speedAdd = 0.5f * curSpeed * curSpeed / this.m_info.m_braking; + float afterWheelRotCurPathOffsetDist = Vector3.Distance(posAfterWheelRot, curPathOffsetPos); + float beforeWheelRotCurPathOffsetDist = Vector3.Distance(posBeforeWheelRot, curPathOffsetPos); + if (Mathf.Min(afterWheelRotCurPathOffsetDist, beforeWheelRotCurPathOffsetDist) >= speedAdd - 1f) { + netMan.m_lanes.m_buffer[laneId].ReserveSpace(leaderLen); + reservedSpaceOnCurrentLane = true; + } + } + } + + if (!reservedSpaceOnCurrentLane && pathMan.m_pathUnits.m_buffer[leaderData.m_path].GetNextPosition(leaderPathPosIndex >> 1, out pathPos)) { + uint nextLaneId = PathManager.GetLaneID(pathPos); + if (nextLaneId != 0u) { + netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(leaderLen); + } + } + } + + if ((ulong)(currentFrameIndex >> 4 & 15u) == (ulong)((long)(leaderID & 15))) { + // check if vehicle can proceed to next path position + + bool canProceeed = false; + uint curLeaderPathId = leaderData.m_path; + int curLeaderPathPosIndex = leaderPathPosIndex >> 1; + int k = 0; + while (k < 5) { + bool invalidPos; + if (PathUnit.GetNextPosition(ref curLeaderPathId, ref curLeaderPathPosIndex, out pathPos, out invalidPos)) { + uint laneId = PathManager.GetLaneID(pathPos); + if (laneId != 0u && !netMan.m_lanes.m_buffer[laneId].CheckSpace(leaderLen)) { + k++; + continue; + } + } + if (invalidPos) { + this.InvalidPath(vehicleID, ref vehicleData, leaderID, ref leaderData); + } + canProceeed = true; + break; + } + if (!canProceeed) { + leaderData.m_flags |= Vehicle.Flags.Congestion; + } + } + } + + float maxSpeed; + if ((leaderData.m_flags & Vehicle.Flags.Stopped) != (Vehicle.Flags)0) { + maxSpeed = 0f; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is stopped. maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is stopped. maxSpeed={maxSpeed}"); + } #endif - } else { - maxSpeed = Mathf.Min(vehicleData.m_targetPos1.w, GetMaxSpeed(leaderID, ref leaderData)); + } else { + maxSpeed = Mathf.Min(vehicleData.m_targetPos1.w, GetMaxSpeed(leaderID, ref leaderData)); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is not stopped. maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is not stopped. maxSpeed={maxSpeed}"); + } #endif - } + } #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Start of second part. curSpeed={curSpeed} curInvRot={curInvRot}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Start of second part. curSpeed={curSpeed} curInvRot={curInvRot}"); + } #endif - afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff; + afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} (old afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag})"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} (old afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag})"); + } #endif - Vector3 zero = Vector3.zero; - bool blocked = false; - float forwardLen = 0f; - if (afterRotToTargetPos1DiffSqrMag > 1f) { // TODO why is this not recalculated? - forward = VectorUtils.NormalizeXZ(afterRotToTargetPos1Diff, out forwardLen); + Vector3 zero = Vector3.zero; + bool blocked = false; + float forwardLen = 0f; + if (afterRotToTargetPos1DiffSqrMag > 1f) { // TODO why is this not recalculated? + forward = VectorUtils.NormalizeXZ(afterRotToTargetPos1Diff, out forwardLen); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1DiffSqrMag > 1f. forward={forward} forwardLen={forwardLen}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1DiffSqrMag > 1f. forward={forward} forwardLen={forwardLen}"); + } #endif - if (forwardLen > 1f) { - Vector3 fwd = afterRotToTargetPos1Diff; - maxSpeedAdd = Mathf.Max(curSpeed, 2f); - maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; + if (forwardLen > 1f) { + Vector3 fwd = afterRotToTargetPos1Diff; + maxSpeedAdd = Mathf.Max(curSpeed, 2f); + maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): forwardLen > 1f. fwd={fwd} maxSpeedAdd={maxSpeedAdd} maxSpeedAddSqr={maxSpeedAddSqr}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): forwardLen > 1f. fwd={fwd} maxSpeedAdd={maxSpeedAdd} maxSpeedAddSqr={maxSpeedAddSqr}"); + } #endif - if (afterRotToTargetPos1DiffSqrMag > maxSpeedAddSqr) { - float fwdLimiter = maxSpeedAdd / Mathf.Sqrt(afterRotToTargetPos1DiffSqrMag); - fwd.x *= fwdLimiter; - fwd.y *= fwdLimiter; + if (afterRotToTargetPos1DiffSqrMag > maxSpeedAddSqr) { + float fwdLimiter = maxSpeedAdd / Mathf.Sqrt(afterRotToTargetPos1DiffSqrMag); + fwd.x *= fwdLimiter; + fwd.y *= fwdLimiter; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1DiffSqrMag > maxSpeedAddSqr. afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag} maxSpeedAddSqr={maxSpeedAddSqr} fwdLimiter={fwdLimiter} fwd={fwd}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1DiffSqrMag > maxSpeedAddSqr. afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag} maxSpeedAddSqr={maxSpeedAddSqr} fwdLimiter={fwdLimiter} fwd={fwd}"); + } #endif - } + } - if (fwd.z < -1f) { // !!! + if (fwd.z < -1f) { // !!! #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): fwd.z < -1f. fwd={fwd}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): fwd.z < -1f. fwd={fwd}"); + } #endif - if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { - Vector3 targetPos0TargetPos1Diff = vehicleData.m_targetPos1 - vehicleData.m_targetPos0; - if ((curInvRot * targetPos0TargetPos1Diff).z < -0.01f) { // !!! + if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) { + Vector3 targetPos0TargetPos1Diff = vehicleData.m_targetPos1 - vehicleData.m_targetPos0; + if ((curInvRot * targetPos0TargetPos1Diff).z < -0.01f) { // !!! #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): (curInvRot * targetPos0TargetPos1Diff).z < -0.01f. curInvRot={curInvRot} targetPos0TargetPos1Diff={targetPos0TargetPos1Diff}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): (curInvRot * targetPos0TargetPos1Diff).z < -0.01f. curInvRot={curInvRot} targetPos0TargetPos1Diff={targetPos0TargetPos1Diff}"); + } #endif - if (afterRotToTargetPos1Diff.z < Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f) { // !!! + if (afterRotToTargetPos1Diff.z < Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f) { // !!! #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff.z < Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f. fwd={fwd} targetPos0TargetPos1Diff={targetPos0TargetPos1Diff} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff.z < Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f. fwd={fwd} targetPos0TargetPos1Diff={targetPos0TargetPos1Diff} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff}"); + } #endif - // Fix: Trams get stuck - /*fwd.z = 0f; - afterRotToTargetPos1Diff = Vector3.zero;*/ - maxSpeed = 0.5f; // NON-STOCK CODE + // Fix: Trams get stuck + /*fwd.z = 0f; + afterRotToTargetPos1Diff = Vector3.zero;*/ + maxSpeed = 0.5f; // NON-STOCK CODE #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): (1) set maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): (1) set maxSpeed={maxSpeed}"); + } #endif - } else { - posAfterWheelRot = posBeforeWheelRot + Vector3.Normalize(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) * this.m_info.m_generatedInfo.m_wheelBase; - posIndex = -1; - UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, vehicleData.m_targetPos1, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) + 1f, 1f); + } else { + posAfterWheelRot = posBeforeWheelRot + Vector3.Normalize(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) * this.m_info.m_generatedInfo.m_wheelBase; + posIndex = -1; + UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, vehicleData.m_targetPos1, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) + 1f, 1f); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff.z >= Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f. Invoked UpdatePathTargetPositions. posAfterWheelRot={posAfterWheelRot} posBeforeWheelRot={posBeforeWheelRot} this.m_info.m_generatedInfo.m_wheelBase={this.m_info.m_generatedInfo.m_wheelBase}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff.z >= Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f. Invoked UpdatePathTargetPositions. posAfterWheelRot={posAfterWheelRot} posBeforeWheelRot={posBeforeWheelRot} this.m_info.m_generatedInfo.m_wheelBase={this.m_info.m_generatedInfo.m_wheelBase}"); + } #endif - } - } else { - posIndex = -1; - UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); - vehicleData.m_targetPos1 = posAfterWheelRot; - fwd.z = 0f; - afterRotToTargetPos1Diff = Vector3.zero; - maxSpeed = 0f; + } + } else { + posIndex = -1; + UpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f); + vehicleData.m_targetPos1 = posAfterWheelRot; + fwd.z = 0f; + afterRotToTargetPos1Diff = Vector3.zero; + maxSpeed = 0f; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is waiting for a path. posIndex={posIndex} vehicleData.m_targetPos1={vehicleData.m_targetPos1} fwd={fwd} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is waiting for a path. posIndex={posIndex} vehicleData.m_targetPos1={vehicleData.m_targetPos1} fwd={fwd} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} maxSpeed={maxSpeed}"); + } #endif - } - } - motionFactor = 0f; + } + } + motionFactor = 0f; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Reset motion factor. motionFactor={motionFactor}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Reset motion factor. motionFactor={motionFactor}"); + } #endif - } - - forward = VectorUtils.NormalizeXZ(fwd, out forwardLen); - float curve = Mathf.PI/2f /* 1.57079637f*/ * (1f - forward.z); // <- constant: a bit inaccurate PI/2 - if (forwardLen > 1f) { - curve /= forwardLen; - } - float targetDist = forwardLen; + } + + forward = VectorUtils.NormalizeXZ(fwd, out forwardLen); + float curve = Mathf.PI/2f /* 1.57079637f*/ * (1f - forward.z); // <- constant: a bit inaccurate PI/2 + if (forwardLen > 1f) { + curve /= forwardLen; + } + float targetDist = forwardLen; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): targetDist={targetDist} fwd={fwd} curve={curve} maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): targetDist={targetDist} fwd={fwd} curve={curve} maxSpeed={maxSpeed}"); + } #endif - if (vehicleData.m_targetPos1.w < 0.1f) { - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve); - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos1.w, braking * 0.9f)); - } else { - maxSpeed = Mathf.Min(maxSpeed, this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve)); - } + if (vehicleData.m_targetPos1.w < 0.1f) { + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve); + maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos1.w, braking * 0.9f)); + } else { + maxSpeed = Mathf.Min(maxSpeed, this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve)); + } #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [1] maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [1] maxSpeed={maxSpeed}"); + } #endif - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos2.w, braking * 0.9f)); + maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos2.w, braking * 0.9f)); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [2] maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [2] maxSpeed={maxSpeed}"); + } #endif - targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos2 - vehicleData.m_targetPos1); - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos3.w, braking * 0.9f)); + targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos2 - vehicleData.m_targetPos1); + maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos3.w, braking * 0.9f)); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [3] maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [3] maxSpeed={maxSpeed}"); + } #endif - targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos3 - vehicleData.m_targetPos2); - if (vehicleData.m_targetPos3.w < 0.01f) { - targetDist = Mathf.Max(0f, targetDist + (this.m_info.m_generatedInfo.m_wheelBase - this.m_info.m_generatedInfo.m_size.z) * 0.5f); - } - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, 0f, braking * 0.9f)); + targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos3 - vehicleData.m_targetPos2); + if (vehicleData.m_targetPos3.w < 0.01f) { + targetDist = Mathf.Max(0f, targetDist + (this.m_info.m_generatedInfo.m_wheelBase - this.m_info.m_generatedInfo.m_size.z) * 0.5f); + } + maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, 0f, braking * 0.9f)); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [4] maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [4] maxSpeed={maxSpeed}"); + } #endif - CarAI.CheckOtherVehicles(vehicleID, ref vehicleData, ref frameData, ref maxSpeed, ref blocked, ref zero, estimatedFrameDist, braking * 0.9f, lodPhysics); + CarAI.CheckOtherVehicles(vehicleID, ref vehicleData, ref frameData, ref maxSpeed, ref blocked, ref zero, estimatedFrameDist, braking * 0.9f, lodPhysics); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): CheckOtherVehicles finished. blocked={blocked}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): CheckOtherVehicles finished. blocked={blocked}"); + } #endif - if (maxSpeed < curSpeed) { - float brake = Mathf.Max(acceleration, Mathf.Min(braking, curSpeed)); - targetSpeed = Mathf.Max(maxSpeed, curSpeed - brake); + if (maxSpeed < curSpeed) { + float brake = Mathf.Max(acceleration, Mathf.Min(braking, curSpeed)); + targetSpeed = Mathf.Max(maxSpeed, curSpeed - brake); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): maxSpeed < curSpeed. maxSpeed={maxSpeed} curSpeed={curSpeed} brake={brake} targetSpeed={targetSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): maxSpeed < curSpeed. maxSpeed={maxSpeed} curSpeed={curSpeed} brake={brake} targetSpeed={targetSpeed}"); + } #endif - } else { - float accel = Mathf.Max(acceleration, Mathf.Min(braking, -curSpeed)); - targetSpeed = Mathf.Min(maxSpeed, curSpeed + accel); + } else { + float accel = Mathf.Max(acceleration, Mathf.Min(braking, -curSpeed)); + targetSpeed = Mathf.Min(maxSpeed, curSpeed + accel); #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): maxSpeed >= curSpeed. maxSpeed={maxSpeed} curSpeed={curSpeed} accel={accel} targetSpeed={targetSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): maxSpeed >= curSpeed. maxSpeed={maxSpeed} curSpeed={curSpeed} accel={accel} targetSpeed={targetSpeed}"); + } #endif - } - } - } else if (curSpeed < 0.1f && hasValidPathTargetPos && leaderInfo.m_vehicleAI.ArriveAtDestination(leaderID, ref leaderData)) { - leaderData.Unspawn(leaderID); - return; - } - if ((leaderData.m_flags & Vehicle.Flags.Stopped) == (Vehicle.Flags)0 && maxSpeed < 0.1f) { + } + } + } else if (curSpeed < 0.1f && hasValidPathTargetPos && leaderInfo.m_vehicleAI.ArriveAtDestination(leaderID, ref leaderData)) { + leaderData.Unspawn(leaderID); + return; + } + if ((leaderData.m_flags & Vehicle.Flags.Stopped) == (Vehicle.Flags)0 && maxSpeed < 0.1f) { #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is not stopped but maxSpeed < 0.1. maxSpeed={maxSpeed}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is not stopped but maxSpeed < 0.1. maxSpeed={maxSpeed}"); + } #endif - blocked = true; - } - if (blocked) { - leaderData.m_blockCounter = (byte)Mathf.Min((int)(leaderData.m_blockCounter + 1), 255); - } else { - leaderData.m_blockCounter = 0; - } - if (forwardLen > 1f) { - turnAngle = Mathf.Asin(forward.x) * Mathf.Sign(targetSpeed); - targetMotion = forward * targetSpeed; - } else { - Vector3 vel = Vector3.ClampMagnitude(afterRotToTargetPos1Diff * 0.5f - curveTangent, braking); - targetMotion = curveTangent + vel; - } - } - bool mayBlink = (currentFrameIndex + (uint)leaderID & 16u) != 0u; - Vector3 springs = targetMotion - curveTangent; - Vector3 targetAfterWheelRotMotion = frameData.m_rotation * targetMotion; - Vector3 targetBeforeWheelRotMotion = Vector3.Normalize((Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) * (targetMotion.magnitude * motionFactor); - targetBeforeWheelRotMotion -= targetAfterWheelRotMotion * (Vector3.Dot(targetAfterWheelRotMotion, targetBeforeWheelRotMotion) / Mathf.Max(1f, targetAfterWheelRotMotion.sqrMagnitude)); - posAfterWheelRot += targetAfterWheelRotMotion; - posBeforeWheelRot += targetBeforeWheelRotMotion; - frameData.m_rotation = Quaternion.LookRotation(posAfterWheelRot - posBeforeWheelRot); - Vector3 targetPos = posAfterWheelRot - frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); - frameData.m_velocity = targetPos - frameData.m_position; - if (leadingVehicle != 0) { - frameData.m_position += frameData.m_velocity * 0.6f; - } else { - frameData.m_position += frameData.m_velocity * 0.5f; - } - frameData.m_swayVelocity = frameData.m_swayVelocity * (1f - this.m_info.m_dampers) - springs * (1f - this.m_info.m_springs) - frameData.m_swayPosition * this.m_info.m_springs; - frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; - frameData.m_steerAngle = 0f; - frameData.m_travelDistance += targetMotion.z; - if (leadingVehicle != 0) { - frameData.m_lightIntensity = Singleton.instance.m_vehicles.m_buffer[(int)leaderID].GetLastFrameData().m_lightIntensity; - } else { - frameData.m_lightIntensity.x = 5f; - frameData.m_lightIntensity.y = ((springs.z >= -0.1f) ? 0.5f : 5f); - frameData.m_lightIntensity.z = ((turnAngle >= -0.1f || !mayBlink) ? 0f : 5f); - frameData.m_lightIntensity.w = ((turnAngle <= 0.1f || !mayBlink) ? 0f : 5f); - } - frameData.m_underground = ((vehicleData.m_flags & Vehicle.Flags.Underground) != (Vehicle.Flags)0); - frameData.m_transition = ((vehicleData.m_flags & Vehicle.Flags.Transition) != (Vehicle.Flags)0); - //base.SimulationStep(vehicleID, ref vehicleData, ref frameData, leaderID, ref leaderData, lodPhysics); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - public static void UpdatePathTargetPositions(TramBaseAI tramBaseAI, ushort vehicleID, ref Vehicle vehicleData, Vector3 refPos1, Vector3 refPos2, ushort leaderID, ref Vehicle leaderData, ref int index, int max1, int max2, float minSqrDistanceA, float minSqrDistanceB) { - Log.Error($"CustomTramBaseAI.InvokeUpdatePathTargetPositions called! tramBaseAI={tramBaseAI}"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { - Log.Error("CustomTrainAI.GetMaxSpeed called"); - return 0f; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { - Log.Error("CustomTrainAI.CalculateMaxSpeed called"); - return 0f; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { - Log.Error("CustomTrainAI.InitializePath called"); - } - } -} + blocked = true; + } + if (blocked) { + leaderData.m_blockCounter = (byte)Mathf.Min((int)(leaderData.m_blockCounter + 1), 255); + } else { + leaderData.m_blockCounter = 0; + } + if (forwardLen > 1f) { + turnAngle = Mathf.Asin(forward.x) * Mathf.Sign(targetSpeed); + targetMotion = forward * targetSpeed; + } else { + Vector3 vel = Vector3.ClampMagnitude(afterRotToTargetPos1Diff * 0.5f - curveTangent, braking); + targetMotion = curveTangent + vel; + } + } + bool mayBlink = (currentFrameIndex + (uint)leaderID & 16u) != 0u; + Vector3 springs = targetMotion - curveTangent; + Vector3 targetAfterWheelRotMotion = frameData.m_rotation * targetMotion; + Vector3 targetBeforeWheelRotMotion = Vector3.Normalize((Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) * (targetMotion.magnitude * motionFactor); + targetBeforeWheelRotMotion -= targetAfterWheelRotMotion * (Vector3.Dot(targetAfterWheelRotMotion, targetBeforeWheelRotMotion) / Mathf.Max(1f, targetAfterWheelRotMotion.sqrMagnitude)); + posAfterWheelRot += targetAfterWheelRotMotion; + posBeforeWheelRot += targetBeforeWheelRotMotion; + frameData.m_rotation = Quaternion.LookRotation(posAfterWheelRot - posBeforeWheelRot); + Vector3 targetPos = posAfterWheelRot - frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f); + frameData.m_velocity = targetPos - frameData.m_position; + if (leadingVehicle != 0) { + frameData.m_position += frameData.m_velocity * 0.6f; + } else { + frameData.m_position += frameData.m_velocity * 0.5f; + } + frameData.m_swayVelocity = frameData.m_swayVelocity * (1f - this.m_info.m_dampers) - springs * (1f - this.m_info.m_springs) - frameData.m_swayPosition * this.m_info.m_springs; + frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f; + frameData.m_steerAngle = 0f; + frameData.m_travelDistance += targetMotion.z; + if (leadingVehicle != 0) { + frameData.m_lightIntensity = Singleton.instance.m_vehicles.m_buffer[(int)leaderID].GetLastFrameData().m_lightIntensity; + } else { + frameData.m_lightIntensity.x = 5f; + frameData.m_lightIntensity.y = ((springs.z >= -0.1f) ? 0.5f : 5f); + frameData.m_lightIntensity.z = ((turnAngle >= -0.1f || !mayBlink) ? 0f : 5f); + frameData.m_lightIntensity.w = ((turnAngle <= 0.1f || !mayBlink) ? 0f : 5f); + } + frameData.m_underground = ((vehicleData.m_flags & Vehicle.Flags.Underground) != (Vehicle.Flags)0); + frameData.m_transition = ((vehicleData.m_flags & Vehicle.Flags.Transition) != (Vehicle.Flags)0); + //base.SimulationStep(vehicleID, ref vehicleData, ref frameData, leaderID, ref leaderData, lodPhysics); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void UpdatePathTargetPositions(TramBaseAI tramBaseAI, ushort vehicleID, ref Vehicle vehicleData, Vector3 refPos1, Vector3 refPos2, ushort leaderID, ref Vehicle leaderData, ref int index, int max1, int max2, float minSqrDistanceA, float minSqrDistanceB) { + Log.Error($"CustomTramBaseAI.InvokeUpdatePathTargetPositions called! tramBaseAI={tramBaseAI}"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { + Log.Error("CustomTrainAI.GetMaxSpeed called"); + return 0f; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { + Log.Error("CustomTrainAI.CalculateMaxSpeed called"); + return 0f; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { + Log.Error("CustomTrainAI.InitializePath called"); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTransportLineAI.cs b/TLM/TLM/Custom/AI/CustomTransportLineAI.cs index 44101a753..9fc7794b2 100644 --- a/TLM/TLM/Custom/AI/CustomTransportLineAI.cs +++ b/TLM/TLM/Custom/AI/CustomTransportLineAI.cs @@ -1,196 +1,189 @@ -using ColossalFramework; -using CSUtil.Commons; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; - -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TransportLineAI))] - public class CustomTransportLineAI : TransportLineAI { // TODO inherit from NetAI (in order to keep the correct references to `base`) - [RedirectMethod] - public static bool CustomStartPathFind(ushort segmentID, ref NetSegment data, ItemClass.Service netService, ItemClass.Service netService2, VehicleInfo.VehicleType vehicleType, bool skipQueue) { - if (data.m_path != 0u) { - Singleton.instance.ReleasePath(data.m_path); - data.m_path = 0u; - } - - NetManager netManager = Singleton.instance; - if ((netManager.m_nodes.m_buffer[(int)data.m_startNode].m_flags & NetNode.Flags.Ambiguous) != NetNode.Flags.None) { - for (int i = 0; i < 8; i++) { - ushort segment = netManager.m_nodes.m_buffer[(int)data.m_startNode].GetSegment(i); - if (segment != 0 && segment != segmentID && netManager.m_segments.m_buffer[(int)segment].m_path != 0u) { - return true; - } - } - } - if ((netManager.m_nodes.m_buffer[(int)data.m_endNode].m_flags & NetNode.Flags.Ambiguous) != NetNode.Flags.None) { - for (int j = 0; j < 8; j++) { - ushort segment2 = netManager.m_nodes.m_buffer[(int)data.m_endNode].GetSegment(j); - if (segment2 != 0 && segment2 != segmentID && netManager.m_segments.m_buffer[(int)segment2].m_path != 0u) { - return true; - } - } - } - - Vector3 position = netManager.m_nodes.m_buffer[(int)data.m_startNode].m_position; - Vector3 position2 = netManager.m_nodes.m_buffer[(int)data.m_endNode].m_position; +namespace TrafficManager.Custom.AI { + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using State; + using UnityEngine; + + [TargetType(typeof(TransportLineAI))] + public class CustomTransportLineAI : TransportLineAI { // TODO inherit from NetAI (in order to keep the correct references to `base`) + [RedirectMethod] + public static bool CustomStartPathFind(ushort segmentID, ref NetSegment data, ItemClass.Service netService, ItemClass.Service netService2, VehicleInfo.VehicleType vehicleType, bool skipQueue) { + if (data.m_path != 0u) { + Singleton.instance.ReleasePath(data.m_path); + data.m_path = 0u; + } + + NetManager netManager = Singleton.instance; + if ((netManager.m_nodes.m_buffer[(int)data.m_startNode].m_flags & NetNode.Flags.Ambiguous) != NetNode.Flags.None) { + for (int i = 0; i < 8; i++) { + ushort segment = netManager.m_nodes.m_buffer[(int)data.m_startNode].GetSegment(i); + if (segment != 0 && segment != segmentID && netManager.m_segments.m_buffer[(int)segment].m_path != 0u) { + return true; + } + } + } + if ((netManager.m_nodes.m_buffer[(int)data.m_endNode].m_flags & NetNode.Flags.Ambiguous) != NetNode.Flags.None) { + for (int j = 0; j < 8; j++) { + ushort segment2 = netManager.m_nodes.m_buffer[(int)data.m_endNode].GetSegment(j); + if (segment2 != 0 && segment2 != segmentID && netManager.m_segments.m_buffer[(int)segment2].m_path != 0u) { + return true; + } + } + } + + Vector3 position = netManager.m_nodes.m_buffer[(int)data.m_startNode].m_position; + Vector3 position2 = netManager.m_nodes.m_buffer[(int)data.m_endNode].m_position; #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[18]; - if (debug) - Log._Debug($"TransportLineAI.CustomStartPathFind({segmentID}, ..., {netService}, {netService2}, {vehicleType}, {skipQueue}): startNode={data.m_startNode} @ {position}, endNode={data.m_endNode} @ {position2} -- line: {netManager.m_nodes.m_buffer[(int)data.m_startNode].m_transportLine}/{netManager.m_nodes.m_buffer[(int)data.m_endNode].m_transportLine}"); + bool debug = GlobalConfig.Instance.Debug.Switches[18]; + if (debug) + Log._Debug($"TransportLineAI.CustomStartPathFind({segmentID}, ..., {netService}, {netService2}, {vehicleType}, {skipQueue}): startNode={data.m_startNode} @ {position}, endNode={data.m_endNode} @ {position2} -- line: {netManager.m_nodes.m_buffer[(int)data.m_startNode].m_transportLine}/{netManager.m_nodes.m_buffer[(int)data.m_endNode].m_transportLine}"); #endif - PathUnit.Position startPosA; - PathUnit.Position startPosB; - float startSqrDistA; - float startSqrDistB; - if (!CustomPathManager.FindPathPosition(position, netService, netService2, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, vehicleType, true, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB)) { - CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); - return true; - } - - PathUnit.Position endPosA; - PathUnit.Position endPosB; - float endSqrDistA; - float endSqrDistB; - if (!CustomPathManager.FindPathPosition(position2, netService, netService2, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, vehicleType, true, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { - CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); - return true; - } - - if ((netManager.m_nodes.m_buffer[(int)data.m_startNode].m_flags & NetNode.Flags.Fixed) != NetNode.Flags.None) { - startPosB = default(PathUnit.Position); - } - - if ((netManager.m_nodes.m_buffer[(int)data.m_endNode].m_flags & NetNode.Flags.Fixed) != NetNode.Flags.None) { - endPosB = default(PathUnit.Position); - } - - if (vehicleType != VehicleInfo.VehicleType.None) { - startPosA.m_offset = 128; - startPosB.m_offset = 128; - endPosA.m_offset = 128; - endPosB.m_offset = 128; - } else { - startPosA.m_offset = (byte)Mathf.Clamp(startPosA.m_offset, 1, 254); - startPosB.m_offset = (byte)Mathf.Clamp(startPosB.m_offset, 1, 254); - endPosA.m_offset = (byte)Mathf.Clamp(endPosA.m_offset, 1, 254); - endPosB.m_offset = (byte)Mathf.Clamp(endPosB.m_offset, 1, 254); - } - - bool stopLane = CustomTransportLineAI.GetStopLane(ref startPosA, vehicleType); - bool stopLane2 = CustomTransportLineAI.GetStopLane(ref startPosB, vehicleType); - bool stopLane3 = CustomTransportLineAI.GetStopLane(ref endPosA, vehicleType); - bool stopLane4 = CustomTransportLineAI.GetStopLane(ref endPosB, vehicleType); - - if ((!stopLane && !stopLane2) || (!stopLane3 && !stopLane4)) { - CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); - return true; - } - - ExtVehicleType extVehicleType = ExtVehicleType.None; - if ((vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.Bus; - if ((vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.PassengerTrain; - if ((vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.Tram; - if ((vehicleType & VehicleInfo.VehicleType.Ship) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.PassengerShip; - if ((vehicleType & VehicleInfo.VehicleType.Plane) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.PassengerPlane; - if ((vehicleType & VehicleInfo.VehicleType.Ferry) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.Ferry; - if ((vehicleType & VehicleInfo.VehicleType.Blimp) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.Blimp; - if ((vehicleType & VehicleInfo.VehicleType.CableCar) != VehicleInfo.VehicleType.None) - extVehicleType = ExtVehicleType.CableCar; - - //Log._Debug($"Transport line. extVehicleType={extVehicleType}"); - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = extVehicleType; - args.vehicleId = 0; - args.spawned = true; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = default(PathUnit.Position); - args.vehicleTypes = vehicleType; - args.isHeavyVehicle = false; - args.hasCombustionEngine = false; - args.ignoreBlocked = true; - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = true; - args.skipQueue = skipQueue; - - if (vehicleType == VehicleInfo.VehicleType.None) { - args.laneTypes = NetInfo.LaneType.Pedestrian; - args.maxLength = 160000f; - } else { - args.laneTypes = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - args.maxLength = 20000f; - } - - if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END - if (startPosA.m_segment != 0 && startPosB.m_segment != 0) { - netManager.m_nodes.m_buffer[data.m_startNode].m_flags |= NetNode.Flags.Ambiguous; - } else { - netManager.m_nodes.m_buffer[data.m_startNode].m_flags &= ~NetNode.Flags.Ambiguous; - } - if (endPosA.m_segment != 0 && endPosB.m_segment != 0) { - netManager.m_nodes.m_buffer[data.m_endNode].m_flags |= NetNode.Flags.Ambiguous; - } else { - netManager.m_nodes.m_buffer[data.m_endNode].m_flags &= ~NetNode.Flags.Ambiguous; - } - data.m_path = path; - data.m_flags |= NetSegment.Flags.WaitingPath; + PathUnit.Position startPosA; + PathUnit.Position startPosB; + float startSqrDistA; + float startSqrDistB; + if (!CustomPathManager.FindPathPosition(position, netService, netService2, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, vehicleType, true, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB)) { + CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); + return true; + } + + PathUnit.Position endPosA; + PathUnit.Position endPosB; + float endSqrDistA; + float endSqrDistB; + if (!CustomPathManager.FindPathPosition(position2, netService, netService2, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, vehicleType, true, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) { + CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); + return true; + } + + if ((netManager.m_nodes.m_buffer[(int)data.m_startNode].m_flags & NetNode.Flags.Fixed) != NetNode.Flags.None) { + startPosB = default(PathUnit.Position); + } + + if ((netManager.m_nodes.m_buffer[(int)data.m_endNode].m_flags & NetNode.Flags.Fixed) != NetNode.Flags.None) { + endPosB = default(PathUnit.Position); + } + + if (vehicleType != VehicleInfo.VehicleType.None) { + startPosA.m_offset = 128; + startPosB.m_offset = 128; + endPosA.m_offset = 128; + endPosB.m_offset = 128; + } else { + startPosA.m_offset = (byte)Mathf.Clamp(startPosA.m_offset, 1, 254); + startPosB.m_offset = (byte)Mathf.Clamp(startPosB.m_offset, 1, 254); + endPosA.m_offset = (byte)Mathf.Clamp(endPosA.m_offset, 1, 254); + endPosB.m_offset = (byte)Mathf.Clamp(endPosB.m_offset, 1, 254); + } + + bool stopLane = CustomTransportLineAI.GetStopLane(ref startPosA, vehicleType); + bool stopLane2 = CustomTransportLineAI.GetStopLane(ref startPosB, vehicleType); + bool stopLane3 = CustomTransportLineAI.GetStopLane(ref endPosA, vehicleType); + bool stopLane4 = CustomTransportLineAI.GetStopLane(ref endPosB, vehicleType); + + if ((!stopLane && !stopLane2) || (!stopLane3 && !stopLane4)) { + CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); + return true; + } + + ExtVehicleType extVehicleType = ExtVehicleType.None; + if ((vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.Bus; + if ((vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.PassengerTrain; + if ((vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.Tram; + if ((vehicleType & VehicleInfo.VehicleType.Ship) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.PassengerShip; + if ((vehicleType & VehicleInfo.VehicleType.Plane) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.PassengerPlane; + if ((vehicleType & VehicleInfo.VehicleType.Ferry) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.Ferry; + if ((vehicleType & VehicleInfo.VehicleType.Blimp) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.Blimp; + if ((vehicleType & VehicleInfo.VehicleType.CableCar) != VehicleInfo.VehicleType.None) + extVehicleType = ExtVehicleType.CableCar; + + //Log._Debug($"Transport line. extVehicleType={extVehicleType}"); + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = extVehicleType; + args.vehicleId = 0; + args.spawned = true; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = default(PathUnit.Position); + args.vehicleTypes = vehicleType; + args.isHeavyVehicle = false; + args.hasCombustionEngine = false; + args.ignoreBlocked = true; + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = true; + args.skipQueue = skipQueue; + + if (vehicleType == VehicleInfo.VehicleType.None) { + args.laneTypes = NetInfo.LaneType.Pedestrian; + args.maxLength = 160000f; + } else { + args.laneTypes = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + args.maxLength = 20000f; + } + + if (CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + // NON-STOCK CODE END + if (startPosA.m_segment != 0 && startPosB.m_segment != 0) { + netManager.m_nodes.m_buffer[data.m_startNode].m_flags |= NetNode.Flags.Ambiguous; + } else { + netManager.m_nodes.m_buffer[data.m_startNode].m_flags &= ~NetNode.Flags.Ambiguous; + } + if (endPosA.m_segment != 0 && endPosB.m_segment != 0) { + netManager.m_nodes.m_buffer[data.m_endNode].m_flags |= NetNode.Flags.Ambiguous; + } else { + netManager.m_nodes.m_buffer[data.m_endNode].m_flags &= ~NetNode.Flags.Ambiguous; + } + data.m_path = path; + data.m_flags |= NetSegment.Flags.WaitingPath; #if DEBUG - if (debug) - Log._Debug($"TransportLineAI.CustomStartPathFind({segmentID}, ..., {netService}, {netService2}, {vehicleType}, {skipQueue}): Started calculating path {path} for extVehicleType={extVehicleType}, startPosA=[seg={startPosA.m_segment}, lane={startPosA.m_lane}, off={startPosA.m_offset}], startPosB=[seg={startPosB.m_segment}, lane={startPosB.m_lane}, off={startPosB.m_offset}], endPosA=[seg={endPosA.m_segment}, lane={endPosA.m_lane}, off={endPosA.m_offset}], endPosB=[seg={endPosB.m_segment}, lane={endPosB.m_lane}, off={endPosB.m_offset}]"); + if (debug) + Log._Debug($"TransportLineAI.CustomStartPathFind({segmentID}, ..., {netService}, {netService2}, {vehicleType}, {skipQueue}): Started calculating path {path} for extVehicleType={extVehicleType}, startPosA=[seg={startPosA.m_segment}, lane={startPosA.m_lane}, off={startPosA.m_offset}], startPosB=[seg={startPosB.m_segment}, lane={startPosB.m_lane}, off={startPosB.m_offset}], endPosA=[seg={endPosA.m_segment}, lane={endPosA.m_lane}, off={endPosA.m_offset}], endPosB=[seg={endPosB.m_segment}, lane={endPosB.m_lane}, off={endPosB.m_offset}]"); #endif - return false; - } - - CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); - return true; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static bool GetStopLane(ref PathUnit.Position pos, VehicleInfo.VehicleType vehicleType) { - Log.Error($"CustomTransportLineAI.GetStopLane called."); - return false; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void CheckSegmentProblems(ushort segmentID, ref NetSegment data) { - Log.Error($"CustomTransportLineAI.CheckSegmentProblems called."); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void CheckNodeProblems(ushort nodeID, ref NetNode data) { - Log.Error($"CustomTransportLineAI.CheckNodeProblems called."); - } - } -} + return false; + } + + CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); + return true; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool GetStopLane(ref PathUnit.Position pos, VehicleInfo.VehicleType vehicleType) { + Log.Error($"CustomTransportLineAI.GetStopLane called."); + return false; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void CheckSegmentProblems(ushort segmentID, ref NetSegment data) { + Log.Error($"CustomTransportLineAI.CheckSegmentProblems called."); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void CheckNodeProblems(ushort nodeID, ref NetNode data) { + Log.Error($"CustomTransportLineAI.CheckNodeProblems called."); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/PathFinding/CustomPathFind.cs b/TLM/TLM/Custom/PathFinding/CustomPathFind.cs index a615d4edc..ba86aafb0 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathFind.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathFind.cs @@ -1,670 +1,675 @@ #define DEBUGLOCKSx #define COUNTSEGMENTSTONEXTJUNCTIONx -using System; -using System.Reflection; -using System.Threading; -using ColossalFramework; -using ColossalFramework.Math; -using ColossalFramework.UI; -using TrafficManager.Geometry; -using UnityEngine; -using System.Collections.Generic; -using TrafficManager.Custom.AI; -using TrafficManager.TrafficLight; -using TrafficManager.State; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using CSUtil.Commons; -using TrafficManager.Manager.Impl; -using static TrafficManager.Custom.PathFinding.CustomPathManager; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - namespace TrafficManager.Custom.PathFinding { + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Threading; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + #if !PF2 [TargetType(typeof(PathFind))] #endif - public class CustomPathFind : PathFind { - private struct BufferItem { - public PathUnit.Position m_position; - public float m_comparisonValue; - public float m_methodDistance; - public float m_duration; - public uint m_laneID; - public NetInfo.Direction m_direction; - public NetInfo.LaneType m_lanesUsed; - public VehicleInfo.VehicleType m_vehiclesUsed; - public float m_trafficRand; + public class CustomPathFind : PathFind { + private struct BufferItem { + public PathUnit.Position m_position; + public float m_comparisonValue; + public float m_methodDistance; + public float m_duration; + public uint m_laneID; + public NetInfo.Direction m_direction; + public NetInfo.LaneType m_lanesUsed; + public VehicleInfo.VehicleType m_vehiclesUsed; + public float m_trafficRand; #if COUNTSEGMENTSTONEXTJUNCTION public uint m_numSegmentsToNextJunction; #endif - } - - private enum LaneChangingCostCalculationMode { - None, - ByLaneDistance, - ByGivenDistance - } - - private const float SEGMENT_MIN_AVERAGE_LENGTH = 30f; - private const float LANE_DENSITY_DISCRETIZATION = 25f; - private const float LANE_USAGE_DISCRETIZATION = 25f; - private const float BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR = Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - - //Expose the private fields - FieldInfo _fieldpathUnits; - FieldInfo _fieldQueueFirst; - FieldInfo _fieldQueueLast; - FieldInfo _fieldQueueLock; - FieldInfo _fieldCalculating; - FieldInfo _fieldTerminated; - FieldInfo _fieldPathFindThread; - - private Array32 PathUnits { - get { return _fieldpathUnits.GetValue(this) as Array32; } - set { _fieldpathUnits.SetValue(this, value); } - } - - private uint QueueFirst { - get { return (uint)_fieldQueueFirst.GetValue(this); } - set { _fieldQueueFirst.SetValue(this, value); } - } - - private uint QueueLast { - get { return (uint)_fieldQueueLast.GetValue(this); } - set { _fieldQueueLast.SetValue(this, value); } - } - - private uint Calculating { - get { return (uint)_fieldCalculating.GetValue(this); } - set { _fieldCalculating.SetValue(this, value); } - } - - private object QueueLock { - get { return _fieldQueueLock.GetValue(this); } - set { _fieldQueueLock.SetValue(this, value); } - } - - private object _bufferLock; - internal Thread CustomPathFindThread { - get { return (Thread)_fieldPathFindThread.GetValue(this); } - set { _fieldPathFindThread.SetValue(this, value); } - } - - private bool Terminated { - get { return (bool)_fieldTerminated.GetValue(this); } - set { _fieldTerminated.SetValue(this, value); } - } - private int m_bufferMinPos; - private int m_bufferMaxPos; - private uint[] m_laneLocation; - private PathUnit.Position[] m_laneTarget; - private BufferItem[] m_buffer; - private int[] m_bufferMin; - private int[] m_bufferMax; - private float m_maxLength; - private uint m_startLaneA; - private uint m_startLaneB; - private ushort m_startSegmentA; - private ushort m_startSegmentB; - private uint m_endLaneA; - private uint m_endLaneB; - private uint m_vehicleLane; - private byte m_startOffsetA; - private byte m_startOffsetB; - private byte m_vehicleOffset; - private NetSegment.Flags m_carBanMask; - private bool m_isHeavyVehicle; - private bool m_ignoreBlocked; - private bool m_stablePath; - private bool m_randomParking; - private bool m_transportVehicle; - private bool m_ignoreCost; - private PathUnitQueueItem queueItem; - private NetSegment.Flags m_disableMask; - /*private ExtVehicleType? _extVehicleType; - private ushort? _vehicleId; - private ExtCitizenInstance.ExtPathType? _extPathType;*/ - private bool m_isRoadVehicle; - private bool m_isLaneArrowObeyingEntity; - private bool m_isLaneConnectionObeyingEntity; - private bool m_leftHandDrive; - //private float _speedRand; - //private bool _extPublicTransport; - //private static ushort laneChangeRandCounter = 0; + } + + private enum LaneChangingCostCalculationMode { + None, + ByLaneDistance, + ByGivenDistance + } + + private const float SEGMENT_MIN_AVERAGE_LENGTH = 30f; + private const float LANE_DENSITY_DISCRETIZATION = 25f; + private const float LANE_USAGE_DISCRETIZATION = 25f; + private const float BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR = Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + + //Expose the private fields + FieldInfo _fieldpathUnits; + FieldInfo _fieldQueueFirst; + FieldInfo _fieldQueueLast; + FieldInfo _fieldQueueLock; + FieldInfo _fieldCalculating; + FieldInfo _fieldTerminated; + FieldInfo _fieldPathFindThread; + + private Array32 PathUnits { + get { return _fieldpathUnits.GetValue(this) as Array32; } + set { _fieldpathUnits.SetValue(this, value); } + } + + private uint QueueFirst { + get { return (uint)_fieldQueueFirst.GetValue(this); } + set { _fieldQueueFirst.SetValue(this, value); } + } + + private uint QueueLast { + get { return (uint)_fieldQueueLast.GetValue(this); } + set { _fieldQueueLast.SetValue(this, value); } + } + + private uint Calculating { + get { return (uint)_fieldCalculating.GetValue(this); } + set { _fieldCalculating.SetValue(this, value); } + } + + private object QueueLock { + get { return _fieldQueueLock.GetValue(this); } + set { _fieldQueueLock.SetValue(this, value); } + } + + private object _bufferLock; + internal Thread CustomPathFindThread { + get { return (Thread)_fieldPathFindThread.GetValue(this); } + set { _fieldPathFindThread.SetValue(this, value); } + } + + private bool Terminated { + get { return (bool)_fieldTerminated.GetValue(this); } + set { _fieldTerminated.SetValue(this, value); } + } + private int m_bufferMinPos; + private int m_bufferMaxPos; + private uint[] m_laneLocation; + private PathUnit.Position[] m_laneTarget; + private BufferItem[] m_buffer; + private int[] m_bufferMin; + private int[] m_bufferMax; + private float m_maxLength; + private uint m_startLaneA; + private uint m_startLaneB; + private ushort m_startSegmentA; + private ushort m_startSegmentB; + private uint m_endLaneA; + private uint m_endLaneB; + private uint m_vehicleLane; + private byte m_startOffsetA; + private byte m_startOffsetB; + private byte m_vehicleOffset; + private NetSegment.Flags m_carBanMask; + private bool m_isHeavyVehicle; + private bool m_ignoreBlocked; + private bool m_stablePath; + private bool m_randomParking; + private bool m_transportVehicle; + private bool m_ignoreCost; + private PathUnitQueueItem queueItem; + private NetSegment.Flags m_disableMask; + /*private ExtVehicleType? _extVehicleType; + private ushort? _vehicleId; + private ExtCitizenInstance.ExtPathType? _extPathType;*/ + private bool m_isRoadVehicle; + private bool m_isLaneArrowObeyingEntity; + private bool m_isLaneConnectionObeyingEntity; + private bool m_leftHandDrive; + //private float _speedRand; + //private bool _extPublicTransport; + //private static ushort laneChangeRandCounter = 0; #if DEBUG - public uint m_failedPathFinds = 0; - public uint m_succeededPathFinds = 0; - private bool m_debug = false; - private IDictionary> m_debugPositions = null; + public uint m_failedPathFinds = 0; + public uint m_succeededPathFinds = 0; + private bool m_debug = false; + private IDictionary> m_debugPositions = null; #endif - public int pfId = 0; - private Randomizer m_pathRandomizer; - private uint m_pathFindIndex; - private NetInfo.LaneType m_laneTypes; - private VehicleInfo.VehicleType m_vehicleTypes; + public int pfId = 0; + private Randomizer m_pathRandomizer; + private uint m_pathFindIndex; + private NetInfo.LaneType m_laneTypes; + private VehicleInfo.VehicleType m_vehicleTypes; - private GlobalConfig m_conf = null; + private GlobalConfig m_conf = null; - private static readonly CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; - private static readonly JunctionRestrictionsManager junctionManager = JunctionRestrictionsManager.Instance; - private static readonly VehicleRestrictionsManager vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; - private static readonly SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance; - private static readonly TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance; - private static readonly RoutingManager routingManager = RoutingManager.Instance; + private static readonly CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; + private static readonly JunctionRestrictionsManager junctionManager = JunctionRestrictionsManager.Instance; + private static readonly VehicleRestrictionsManager vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; + private static readonly SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance; + private static readonly TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance; + private static readonly RoutingManager routingManager = RoutingManager.Instance; - public bool IsMasterPathFind = false; + public bool IsMasterPathFind = false; - protected virtual void Awake() { + protected virtual void Awake() { #if DEBUG - Log._Debug($"CustomPathFind.Awake called."); + Log._Debug($"CustomPathFind.Awake called."); #endif - var stockPathFindType = typeof(PathFind); - const BindingFlags fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; + var stockPathFindType = typeof(PathFind); + const BindingFlags fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; - _fieldpathUnits = stockPathFindType.GetField("m_pathUnits", fieldFlags); - _fieldQueueFirst = stockPathFindType.GetField("m_queueFirst", fieldFlags); - _fieldQueueLast = stockPathFindType.GetField("m_queueLast", fieldFlags); - _fieldQueueLock = stockPathFindType.GetField("m_queueLock", fieldFlags); - _fieldTerminated = stockPathFindType.GetField("m_terminated", fieldFlags); - _fieldCalculating = stockPathFindType.GetField("m_calculating", fieldFlags); - _fieldPathFindThread = stockPathFindType.GetField("m_pathFindThread", fieldFlags); + _fieldpathUnits = stockPathFindType.GetField("m_pathUnits", fieldFlags); + _fieldQueueFirst = stockPathFindType.GetField("m_queueFirst", fieldFlags); + _fieldQueueLast = stockPathFindType.GetField("m_queueLast", fieldFlags); + _fieldQueueLock = stockPathFindType.GetField("m_queueLock", fieldFlags); + _fieldTerminated = stockPathFindType.GetField("m_terminated", fieldFlags); + _fieldCalculating = stockPathFindType.GetField("m_calculating", fieldFlags); + _fieldPathFindThread = stockPathFindType.GetField("m_pathFindThread", fieldFlags); - m_buffer = new BufferItem[65536]; // 2^16 - _bufferLock = PathManager.instance.m_bufferLock; - PathUnits = PathManager.instance.m_pathUnits; + m_buffer = new BufferItem[65536]; // 2^16 + _bufferLock = PathManager.instance.m_bufferLock; + PathUnits = PathManager.instance.m_pathUnits; #if DEBUG - if (QueueLock == null) { - Log._Debug($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.Awake: QueueLock is null. Creating."); - QueueLock = new object(); - } else { - Log._Debug($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.Awake: QueueLock is NOT null."); - } + if (QueueLock == null) { + Log._Debug($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.Awake: QueueLock is null. Creating."); + QueueLock = new object(); + } else { + Log._Debug($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.Awake: QueueLock is NOT null."); + } #else QueueLock = new object(); #endif - m_laneLocation = new uint[262144]; // 2^18 - m_laneTarget = new PathUnit.Position[262144]; // 2^18 - m_bufferMin = new int[1024]; // 2^10 - m_bufferMax = new int[1024]; // 2^10 - - m_pathfindProfiler = new ThreadProfiler(); - CustomPathFindThread = new Thread(PathFindThread) { Name = "Pathfind" }; - CustomPathFindThread.Priority = SimulationManager.SIMULATION_PRIORITY; - CustomPathFindThread.Start(); - if (!CustomPathFindThread.IsAlive) { - //CODebugBase.Error(LogChannel.Core, "Path find thread failed to start!"); - Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) Path find thread failed to start!"); - } + m_laneLocation = new uint[262144]; // 2^18 + m_laneTarget = new PathUnit.Position[262144]; // 2^18 + m_bufferMin = new int[1024]; // 2^10 + m_bufferMax = new int[1024]; // 2^10 + + m_pathfindProfiler = new ThreadProfiler(); + CustomPathFindThread = new Thread(PathFindThread) { Name = "Pathfind" }; + CustomPathFindThread.Priority = SimulationManager.SIMULATION_PRIORITY; + CustomPathFindThread.Start(); + if (!CustomPathFindThread.IsAlive) { + //CODebugBase.Error(LogChannel.Core, "Path find thread failed to start!"); + Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) Path find thread failed to start!"); + } - } + } - protected virtual void OnDestroy() { + protected virtual void OnDestroy() { #if DEBUGLOCKS uint lockIter = 0; #endif - try { - Monitor.Enter(QueueLock); - Terminated = true; - Monitor.PulseAll(QueueLock); - } catch (Exception e) { - Log.Error("CustomPathFind.OnDestroy Error: " + e.ToString()); - } finally { - Monitor.Exit(QueueLock); - } - } + try { + Monitor.Enter(QueueLock); + Terminated = true; + Monitor.PulseAll(QueueLock); + } catch (Exception e) { + Log.Error("CustomPathFind.OnDestroy Error: " + e.ToString()); + } finally { + Monitor.Exit(QueueLock); + } + } #if !PF2 [RedirectMethod] #endif - public new bool CalculatePath(uint unit, bool skipQueue) { - return ExtCalculatePath(unit, skipQueue); - } - - public bool ExtCalculatePath(uint unit, bool skipQueue) { - if (CustomPathManager._instance.AddPathReference(unit)) { - try { - Monitor.Enter(QueueLock); - - if (skipQueue) { - - if (this.QueueLast == 0u) { - this.QueueLast = unit; - } else { - CustomPathManager._instance.queueItems[unit].nextPathUnitId = QueueFirst; - //this.PathUnits.m_buffer[unit].m_nextPathUnit = this.QueueFirst; - } - this.QueueFirst = unit; - } else { - if (this.QueueLast == 0u) { - this.QueueFirst = unit; - } else { - CustomPathManager._instance.queueItems[QueueLast].nextPathUnitId = unit; - //this.PathUnits.m_buffer[this.QueueLast].m_nextPathUnit = unit; - } - this.QueueLast = unit; - } - this.PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_CREATED; - ++this.m_queuedPathFindCount; - - Monitor.Pulse(this.QueueLock); - } catch (Exception e) { - Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.CalculatePath({unit}, {skipQueue}): Error: {e.ToString()}"); - } finally { - Monitor.Exit(this.QueueLock); - } - return true; - } - return false; - } - - // PathFind - protected void PathFindImplementation(uint unit, ref PathUnit data) { - m_conf = GlobalConfig.Instance; // NON-STOCK CODE - - NetManager netManager = Singleton.instance; - this.m_laneTypes = (NetInfo.LaneType)this.PathUnits.m_buffer[unit].m_laneTypes; - this.m_vehicleTypes = (VehicleInfo.VehicleType)this.PathUnits.m_buffer[unit].m_vehicleTypes; - this.m_maxLength = this.PathUnits.m_buffer[unit].m_length; - this.m_pathFindIndex = (this.m_pathFindIndex + 1u & 32767u); - this.m_pathRandomizer = new Randomizer(unit); - - this.m_carBanMask = NetSegment.Flags.CarBan; - this.m_isHeavyVehicle = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 16) != 0); - if (m_isHeavyVehicle) { - this.m_carBanMask |= NetSegment.Flags.HeavyBan; - } - if ((this.PathUnits.m_buffer[unit].m_simulationFlags & 4) != 0) { - this.m_carBanMask |= NetSegment.Flags.WaitingPath; - } - this.m_ignoreBlocked = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 32) != 0); - this.m_stablePath = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 64) != 0); - this.m_randomParking = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 128) != 0); - this.m_transportVehicle = ((byte)(this.m_laneTypes & NetInfo.LaneType.TransportVehicle) != 0); - this.m_ignoreCost = (this.m_stablePath || (this.PathUnits.m_buffer[unit].m_simulationFlags & 8) != 0); - this.m_disableMask = (NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed); - if ((this.PathUnits.m_buffer[unit].m_simulationFlags & 2) == 0) { - this.m_disableMask |= NetSegment.Flags.Flooded; - } - //this._speedRand = 0; - this.m_leftHandDrive = Constants.ServiceFactory.SimulationService.LeftHandDrive; - this.m_isRoadVehicle = (queueItem.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None; - this.m_isLaneArrowObeyingEntity = (m_vehicleTypes & LaneArrowManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None && - (queueItem.vehicleType & LaneArrowManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None; - this.m_isLaneConnectionObeyingEntity = (m_vehicleTypes & LaneConnectionManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None && - (queueItem.vehicleType & LaneConnectionManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None; + public new bool CalculatePath(uint unit, bool skipQueue) { + return ExtCalculatePath(unit, skipQueue); + } + + public bool ExtCalculatePath(uint unit, bool skipQueue) { + if (CustomPathManager._instance.AddPathReference(unit)) { + try { + Monitor.Enter(QueueLock); + + if (skipQueue) { + + if (this.QueueLast == 0u) { + this.QueueLast = unit; + } else { + CustomPathManager._instance.queueItems[unit].nextPathUnitId = QueueFirst; + //this.PathUnits.m_buffer[unit].m_nextPathUnit = this.QueueFirst; + } + this.QueueFirst = unit; + } else { + if (this.QueueLast == 0u) { + this.QueueFirst = unit; + } else { + CustomPathManager._instance.queueItems[QueueLast].nextPathUnitId = unit; + //this.PathUnits.m_buffer[this.QueueLast].m_nextPathUnit = unit; + } + this.QueueLast = unit; + } + this.PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_CREATED; + ++this.m_queuedPathFindCount; + + Monitor.Pulse(this.QueueLock); + } catch (Exception e) { + Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.CalculatePath({unit}, {skipQueue}): Error: {e.ToString()}"); + } finally { + Monitor.Exit(this.QueueLock); + } + return true; + } + return false; + } + + // PathFind + protected void PathFindImplementation(uint unit, ref PathUnit data) { + m_conf = GlobalConfig.Instance; // NON-STOCK CODE + + NetManager netManager = Singleton.instance; + this.m_laneTypes = (NetInfo.LaneType)this.PathUnits.m_buffer[unit].m_laneTypes; + this.m_vehicleTypes = (VehicleInfo.VehicleType)this.PathUnits.m_buffer[unit].m_vehicleTypes; + this.m_maxLength = this.PathUnits.m_buffer[unit].m_length; + this.m_pathFindIndex = (this.m_pathFindIndex + 1u & 32767u); + this.m_pathRandomizer = new Randomizer(unit); + + this.m_carBanMask = NetSegment.Flags.CarBan; + this.m_isHeavyVehicle = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 16) != 0); + if (m_isHeavyVehicle) { + this.m_carBanMask |= NetSegment.Flags.HeavyBan; + } + if ((this.PathUnits.m_buffer[unit].m_simulationFlags & 4) != 0) { + this.m_carBanMask |= NetSegment.Flags.WaitingPath; + } + this.m_ignoreBlocked = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 32) != 0); + this.m_stablePath = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 64) != 0); + this.m_randomParking = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 128) != 0); + this.m_transportVehicle = ((byte)(this.m_laneTypes & NetInfo.LaneType.TransportVehicle) != 0); + this.m_ignoreCost = (this.m_stablePath || (this.PathUnits.m_buffer[unit].m_simulationFlags & 8) != 0); + this.m_disableMask = (NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed); + if ((this.PathUnits.m_buffer[unit].m_simulationFlags & 2) == 0) { + this.m_disableMask |= NetSegment.Flags.Flooded; + } + //this._speedRand = 0; + this.m_leftHandDrive = Constants.ServiceFactory.SimulationService.LeftHandDrive; + this.m_isRoadVehicle = (queueItem.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None; + this.m_isLaneArrowObeyingEntity = + (m_vehicleTypes & LaneArrowManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None + && (queueItem.vehicleType & LaneArrowManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None; + this.m_isLaneConnectionObeyingEntity = + (m_vehicleTypes & LaneConnectionManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None + && (queueItem.vehicleType & LaneConnectionManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None; #if DEBUGNEWPF && DEBUG - bool debug = this.m_debug = m_conf.Debug.Switches[0] && - ((m_conf.Debug.ExtVehicleType == ExtVehicleType.None && queueItem.vehicleType == ExtVehicleType.None) || (queueItem.vehicleType & m_conf.Debug.ExtVehicleType) != ExtVehicleType.None) && - (m_conf.Debug.StartSegmentId == 0 || data.m_position00.m_segment == m_conf.Debug.StartSegmentId || data.m_position02.m_segment == m_conf.Debug.StartSegmentId) && - (m_conf.Debug.EndSegmentId == 0 || data.m_position01.m_segment == m_conf.Debug.EndSegmentId || data.m_position03.m_segment == m_conf.Debug.EndSegmentId) && - (m_conf.Debug.VehicleId == 0 || queueItem.vehicleId == m_conf.Debug.VehicleId) - ; - if (debug) { - Log._Debug($"CustomPathFind.PathFindImplementation: START calculating path unit {unit}, type {queueItem.vehicleType}"); - m_debugPositions = new Dictionary>(); - } -#endif - - if ((byte)(this.m_laneTypes & NetInfo.LaneType.Vehicle) != 0) { - this.m_laneTypes |= NetInfo.LaneType.TransportVehicle; - } - int posCount = (int)(this.PathUnits.m_buffer[unit].m_positionCount & 15); - int vehiclePosIndicator = this.PathUnits.m_buffer[unit].m_positionCount >> 4; - BufferItem bufferItemStartA; - if (data.m_position00.m_segment != 0 && posCount >= 1) { - this.m_startLaneA = PathManager.GetLaneID(data.m_position00); - this.m_startSegmentA = data.m_position00.m_segment; // NON-STOCK CODE - this.m_startOffsetA = data.m_position00.m_offset; - bufferItemStartA.m_laneID = this.m_startLaneA; - bufferItemStartA.m_position = data.m_position00; - this.GetLaneDirection(data.m_position00, out bufferItemStartA.m_direction, out bufferItemStartA.m_lanesUsed, out bufferItemStartA.m_vehiclesUsed); - bufferItemStartA.m_comparisonValue = 0f; - bufferItemStartA.m_duration = 0f; + bool debug = + this.m_debug = + m_conf.Debug.Switches[0] && + ((m_conf.Debug.ApiExtVehicleType == ExtVehicleType.None + && queueItem.vehicleType == ExtVehicleType.None) + || (queueItem.vehicleType & m_conf.Debug.ApiExtVehicleType) != ExtVehicleType.None) + && (m_conf.Debug.StartSegmentId == 0 + || data.m_position00.m_segment == m_conf.Debug.StartSegmentId + || data.m_position02.m_segment == m_conf.Debug.StartSegmentId) + && (m_conf.Debug.EndSegmentId == 0 + || data.m_position01.m_segment == m_conf.Debug.EndSegmentId + || data.m_position03.m_segment == m_conf.Debug.EndSegmentId) + && (m_conf.Debug.VehicleId == 0 + || queueItem.vehicleId == m_conf.Debug.VehicleId); + if (debug) { + Log._Debug($"CustomPathFind.PathFindImplementation: START calculating path unit {unit}, type {queueItem.vehicleType}"); + m_debugPositions = new Dictionary>(); + } +#endif + + if ((byte)(this.m_laneTypes & NetInfo.LaneType.Vehicle) != 0) { + this.m_laneTypes |= NetInfo.LaneType.TransportVehicle; + } + int posCount = (int)(this.PathUnits.m_buffer[unit].m_positionCount & 15); + int vehiclePosIndicator = this.PathUnits.m_buffer[unit].m_positionCount >> 4; + BufferItem bufferItemStartA; + if (data.m_position00.m_segment != 0 && posCount >= 1) { + this.m_startLaneA = PathManager.GetLaneID(data.m_position00); + this.m_startSegmentA = data.m_position00.m_segment; // NON-STOCK CODE + this.m_startOffsetA = data.m_position00.m_offset; + bufferItemStartA.m_laneID = this.m_startLaneA; + bufferItemStartA.m_position = data.m_position00; + this.GetLaneDirection(data.m_position00, out bufferItemStartA.m_direction, out bufferItemStartA.m_lanesUsed, out bufferItemStartA.m_vehiclesUsed); + bufferItemStartA.m_comparisonValue = 0f; + bufferItemStartA.m_duration = 0f; #if COUNTSEGMENTSTONEXTJUNCTION bufferItemStartA.m_numSegmentsToNextJunction = 0; #endif - } else { - this.m_startLaneA = 0u; - this.m_startSegmentA = 0; // NON-STOCK CODE - this.m_startOffsetA = 0; - bufferItemStartA = default(BufferItem); - } - BufferItem bufferItemStartB; - if (data.m_position02.m_segment != 0 && posCount >= 3) { - this.m_startLaneB = PathManager.GetLaneID(data.m_position02); - this.m_startSegmentB = data.m_position02.m_segment; // NON-STOCK CODE - this.m_startOffsetB = data.m_position02.m_offset; - bufferItemStartB.m_laneID = this.m_startLaneB; - bufferItemStartB.m_position = data.m_position02; - this.GetLaneDirection(data.m_position02, out bufferItemStartB.m_direction, out bufferItemStartB.m_lanesUsed, out bufferItemStartB.m_vehiclesUsed); - bufferItemStartB.m_comparisonValue = 0f; - bufferItemStartB.m_duration = 0f; + } else { + this.m_startLaneA = 0u; + this.m_startSegmentA = 0; // NON-STOCK CODE + this.m_startOffsetA = 0; + bufferItemStartA = default(BufferItem); + } + BufferItem bufferItemStartB; + if (data.m_position02.m_segment != 0 && posCount >= 3) { + this.m_startLaneB = PathManager.GetLaneID(data.m_position02); + this.m_startSegmentB = data.m_position02.m_segment; // NON-STOCK CODE + this.m_startOffsetB = data.m_position02.m_offset; + bufferItemStartB.m_laneID = this.m_startLaneB; + bufferItemStartB.m_position = data.m_position02; + this.GetLaneDirection(data.m_position02, out bufferItemStartB.m_direction, out bufferItemStartB.m_lanesUsed, out bufferItemStartB.m_vehiclesUsed); + bufferItemStartB.m_comparisonValue = 0f; + bufferItemStartB.m_duration = 0f; #if COUNTSEGMENTSTONEXTJUNCTION bufferItemStartB.m_numSegmentsToNextJunction = 0; #endif - } else { - this.m_startLaneB = 0u; - this.m_startSegmentB = 0; // NON-STOCK CODE - this.m_startOffsetB = 0; - bufferItemStartB = default(BufferItem); - } - BufferItem bufferItemEndA; - if (data.m_position01.m_segment != 0 && posCount >= 2) { - this.m_endLaneA = PathManager.GetLaneID(data.m_position01); - bufferItemEndA.m_laneID = this.m_endLaneA; - bufferItemEndA.m_position = data.m_position01; - this.GetLaneDirection(data.m_position01, out bufferItemEndA.m_direction, out bufferItemEndA.m_lanesUsed, out bufferItemEndA.m_vehiclesUsed); - bufferItemEndA.m_methodDistance = 0.01f; - bufferItemEndA.m_comparisonValue = 0f; - bufferItemEndA.m_duration = 0f; - bufferItemEndA.m_trafficRand = 0; // NON-STOCK CODE + } else { + this.m_startLaneB = 0u; + this.m_startSegmentB = 0; // NON-STOCK CODE + this.m_startOffsetB = 0; + bufferItemStartB = default(BufferItem); + } + BufferItem bufferItemEndA; + if (data.m_position01.m_segment != 0 && posCount >= 2) { + this.m_endLaneA = PathManager.GetLaneID(data.m_position01); + bufferItemEndA.m_laneID = this.m_endLaneA; + bufferItemEndA.m_position = data.m_position01; + this.GetLaneDirection(data.m_position01, out bufferItemEndA.m_direction, out bufferItemEndA.m_lanesUsed, out bufferItemEndA.m_vehiclesUsed); + bufferItemEndA.m_methodDistance = 0.01f; + bufferItemEndA.m_comparisonValue = 0f; + bufferItemEndA.m_duration = 0f; + bufferItemEndA.m_trafficRand = 0; // NON-STOCK CODE #if COUNTSEGMENTSTONEXTJUNCTION bufferItemEndA.m_numSegmentsToNextJunction = 0; #endif - } else { - this.m_endLaneA = 0u; - bufferItemEndA = default(BufferItem); - } - BufferItem bufferItemEndB; - if (data.m_position03.m_segment != 0 && posCount >= 4) { - this.m_endLaneB = PathManager.GetLaneID(data.m_position03); - bufferItemEndB.m_laneID = this.m_endLaneB; - bufferItemEndB.m_position = data.m_position03; - this.GetLaneDirection(data.m_position03, out bufferItemEndB.m_direction, out bufferItemEndB.m_lanesUsed, out bufferItemEndB.m_vehiclesUsed); - bufferItemEndB.m_methodDistance = 0.01f; - bufferItemEndB.m_comparisonValue = 0f; - bufferItemEndB.m_duration = 0f; - bufferItemEndB.m_trafficRand = 0; // NON-STOCK CODE + } else { + this.m_endLaneA = 0u; + bufferItemEndA = default(BufferItem); + } + BufferItem bufferItemEndB; + if (data.m_position03.m_segment != 0 && posCount >= 4) { + this.m_endLaneB = PathManager.GetLaneID(data.m_position03); + bufferItemEndB.m_laneID = this.m_endLaneB; + bufferItemEndB.m_position = data.m_position03; + this.GetLaneDirection(data.m_position03, out bufferItemEndB.m_direction, out bufferItemEndB.m_lanesUsed, out bufferItemEndB.m_vehiclesUsed); + bufferItemEndB.m_methodDistance = 0.01f; + bufferItemEndB.m_comparisonValue = 0f; + bufferItemEndB.m_duration = 0f; + bufferItemEndB.m_trafficRand = 0; // NON-STOCK CODE #if COUNTSEGMENTSTONEXTJUNCTION bufferItemEndB.m_numSegmentsToNextJunction = 0; #endif - } else { - this.m_endLaneB = 0u; - bufferItemEndB = default(BufferItem); - } - if (data.m_position11.m_segment != 0 && vehiclePosIndicator >= 1) { - this.m_vehicleLane = PathManager.GetLaneID(data.m_position11); - this.m_vehicleOffset = data.m_position11.m_offset; - } else { - this.m_vehicleLane = 0u; - this.m_vehicleOffset = 0; - } + } else { + this.m_endLaneB = 0u; + bufferItemEndB = default(BufferItem); + } + if (data.m_position11.m_segment != 0 && vehiclePosIndicator >= 1) { + this.m_vehicleLane = PathManager.GetLaneID(data.m_position11); + this.m_vehicleOffset = data.m_position11.m_offset; + } else { + this.m_vehicleLane = 0u; + this.m_vehicleOffset = 0; + } #if DEBUGNEWPF && DEBUG - if (debug) { - Log._Debug($"CustomPathFind.PathFindImplementation: Preparing calculating path unit {unit}, type {queueItem.vehicleType}:\n" + - $"\tbufferItemStartA: segment={bufferItemStartA.m_position.m_segment} lane={bufferItemStartA.m_position.m_lane} off={bufferItemStartA.m_position.m_offset} laneId={bufferItemStartA.m_laneID}\n" + - $"\tbufferItemStartB: segment={bufferItemStartB.m_position.m_segment} lane={bufferItemStartB.m_position.m_lane} off={bufferItemStartB.m_position.m_offset} laneId={bufferItemStartB.m_laneID}\n" + - $"\tbufferItemEndA: segment={bufferItemEndA.m_position.m_segment} lane={bufferItemEndA.m_position.m_lane} off={bufferItemEndA.m_position.m_offset} laneId={bufferItemEndA.m_laneID}\n" + - $"\tbufferItemEndB: segment={bufferItemEndB.m_position.m_segment} lane={bufferItemEndB.m_position.m_lane} off={bufferItemEndB.m_position.m_offset} laneId={bufferItemEndB.m_laneID}\n" + - $"\tvehicleItem: segment={data.m_position11.m_segment} lane={data.m_position11.m_lane} off={data.m_position11.m_offset} laneId={m_vehicleLane} vehiclePosIndicator={vehiclePosIndicator}\n" - ); - } -#endif - BufferItem finalBufferItem = default(BufferItem); - byte startOffset = 0; - this.m_bufferMinPos = 0; - this.m_bufferMaxPos = -1; - if (this.m_pathFindIndex == 0u) { - uint maxUInt = 4294901760u; - for (int i = 0; i < 262144; ++i) { - this.m_laneLocation[i] = maxUInt; - } - } - for (int j = 0; j < 1024; ++j) { - this.m_bufferMin[j] = 0; - this.m_bufferMax[j] = -1; - } - if (bufferItemEndA.m_position.m_segment != 0) { - ++this.m_bufferMax[0]; - this.m_buffer[++this.m_bufferMaxPos] = bufferItemEndA; - } - if (bufferItemEndB.m_position.m_segment != 0) { - ++this.m_bufferMax[0]; - this.m_buffer[++this.m_bufferMaxPos] = bufferItemEndB; - } - bool canFindPath = false; - - while (this.m_bufferMinPos <= this.m_bufferMaxPos) { - int bufMin = this.m_bufferMin[this.m_bufferMinPos]; - int bufMax = this.m_bufferMax[this.m_bufferMinPos]; - if (bufMin > bufMax) { - ++this.m_bufferMinPos; - } else { - this.m_bufferMin[this.m_bufferMinPos] = bufMin + 1; - BufferItem candidateItem = this.m_buffer[(this.m_bufferMinPos << 6) + bufMin]; - if (candidateItem.m_position.m_segment == bufferItemStartA.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartA.m_position.m_lane) { - // we reached startA - if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0 && candidateItem.m_position.m_offset >= this.m_startOffsetA) { - finalBufferItem = candidateItem; - startOffset = this.m_startOffsetA; - canFindPath = true; - break; - } - if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0 && candidateItem.m_position.m_offset <= this.m_startOffsetA) { - finalBufferItem = candidateItem; - startOffset = this.m_startOffsetA; - canFindPath = true; - break; - } - } - if (candidateItem.m_position.m_segment == bufferItemStartB.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartB.m_position.m_lane) { - // we reached startB - if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0 && candidateItem.m_position.m_offset >= this.m_startOffsetB) { - finalBufferItem = candidateItem; - startOffset = this.m_startOffsetB; - canFindPath = true; - break; - } - if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0 && candidateItem.m_position.m_offset <= this.m_startOffsetB) { - finalBufferItem = candidateItem; - startOffset = this.m_startOffsetB; - canFindPath = true; - break; - } - } - - // explore the path - if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0) { - ushort startNode = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; - uint laneRoutingIndex = routingManager.GetLaneEndRoutingIndex(candidateItem.m_laneID, true); - this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.SegmentRoutings[candidateItem.m_position.m_segment], routingManager.LaneEndBackwardRoutings[laneRoutingIndex], startNode, true, ref netManager.m_nodes.m_buffer[startNode], 0, false); - } - - if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0) { - ushort endNode = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; - uint laneRoutingIndex = routingManager.GetLaneEndRoutingIndex(candidateItem.m_laneID, false); - this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.SegmentRoutings[candidateItem.m_position.m_segment], routingManager.LaneEndBackwardRoutings[laneRoutingIndex], endNode, false, ref netManager.m_nodes.m_buffer[endNode], 255, false); - } - - // handle special nodes (e.g. bus stops) - int num6 = 0; - ushort specialNodeId = netManager.m_lanes.m_buffer[candidateItem.m_laneID].m_nodes; - if (specialNodeId != 0) { - ushort startNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; - ushort endNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; - bool nodesDisabled = ((netManager.m_nodes.m_buffer[startNode2].m_flags | netManager.m_nodes.m_buffer[endNode2].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None; - while (specialNodeId != 0) { - NetInfo.Direction direction = NetInfo.Direction.None; - byte laneOffset = netManager.m_nodes.m_buffer[specialNodeId].m_laneOffset; - if (laneOffset <= candidateItem.m_position.m_offset) { - direction |= NetInfo.Direction.Forward; - } - if (laneOffset >= candidateItem.m_position.m_offset) { - direction |= NetInfo.Direction.Backward; - } - if ((byte)(candidateItem.m_direction & direction) != 0 && (!nodesDisabled || (netManager.m_nodes.m_buffer[specialNodeId].m_flags & NetNode.Flags.Disabled) != NetNode.Flags.None)) { + if (debug) { + Log._Debug($"CustomPathFind.PathFindImplementation: Preparing calculating path unit {unit}, type {queueItem.vehicleType}:\n" + + $"\tbufferItemStartA: segment={bufferItemStartA.m_position.m_segment} lane={bufferItemStartA.m_position.m_lane} off={bufferItemStartA.m_position.m_offset} laneId={bufferItemStartA.m_laneID}\n" + + $"\tbufferItemStartB: segment={bufferItemStartB.m_position.m_segment} lane={bufferItemStartB.m_position.m_lane} off={bufferItemStartB.m_position.m_offset} laneId={bufferItemStartB.m_laneID}\n" + + $"\tbufferItemEndA: segment={bufferItemEndA.m_position.m_segment} lane={bufferItemEndA.m_position.m_lane} off={bufferItemEndA.m_position.m_offset} laneId={bufferItemEndA.m_laneID}\n" + + $"\tbufferItemEndB: segment={bufferItemEndB.m_position.m_segment} lane={bufferItemEndB.m_position.m_lane} off={bufferItemEndB.m_position.m_offset} laneId={bufferItemEndB.m_laneID}\n" + + $"\tvehicleItem: segment={data.m_position11.m_segment} lane={data.m_position11.m_lane} off={data.m_position11.m_offset} laneId={m_vehicleLane} vehiclePosIndicator={vehiclePosIndicator}\n" + ); + } +#endif + BufferItem finalBufferItem = default(BufferItem); + byte startOffset = 0; + this.m_bufferMinPos = 0; + this.m_bufferMaxPos = -1; + if (this.m_pathFindIndex == 0u) { + uint maxUInt = 4294901760u; + for (int i = 0; i < 262144; ++i) { + this.m_laneLocation[i] = maxUInt; + } + } + for (int j = 0; j < 1024; ++j) { + this.m_bufferMin[j] = 0; + this.m_bufferMax[j] = -1; + } + if (bufferItemEndA.m_position.m_segment != 0) { + ++this.m_bufferMax[0]; + this.m_buffer[++this.m_bufferMaxPos] = bufferItemEndA; + } + if (bufferItemEndB.m_position.m_segment != 0) { + ++this.m_bufferMax[0]; + this.m_buffer[++this.m_bufferMaxPos] = bufferItemEndB; + } + bool canFindPath = false; + + while (this.m_bufferMinPos <= this.m_bufferMaxPos) { + int bufMin = this.m_bufferMin[this.m_bufferMinPos]; + int bufMax = this.m_bufferMax[this.m_bufferMinPos]; + if (bufMin > bufMax) { + ++this.m_bufferMinPos; + } else { + this.m_bufferMin[this.m_bufferMinPos] = bufMin + 1; + BufferItem candidateItem = this.m_buffer[(this.m_bufferMinPos << 6) + bufMin]; + if (candidateItem.m_position.m_segment == bufferItemStartA.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartA.m_position.m_lane) { + // we reached startA + if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0 && candidateItem.m_position.m_offset >= this.m_startOffsetA) { + finalBufferItem = candidateItem; + startOffset = this.m_startOffsetA; + canFindPath = true; + break; + } + if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0 && candidateItem.m_position.m_offset <= this.m_startOffsetA) { + finalBufferItem = candidateItem; + startOffset = this.m_startOffsetA; + canFindPath = true; + break; + } + } + if (candidateItem.m_position.m_segment == bufferItemStartB.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartB.m_position.m_lane) { + // we reached startB + if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0 && candidateItem.m_position.m_offset >= this.m_startOffsetB) { + finalBufferItem = candidateItem; + startOffset = this.m_startOffsetB; + canFindPath = true; + break; + } + if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0 && candidateItem.m_position.m_offset <= this.m_startOffsetB) { + finalBufferItem = candidateItem; + startOffset = this.m_startOffsetB; + canFindPath = true; + break; + } + } + + // explore the path + if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0) { + ushort startNode = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; + uint laneRoutingIndex = routingManager.GetLaneEndRoutingIndex(candidateItem.m_laneID, true); + this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.SegmentRoutings[candidateItem.m_position.m_segment], routingManager.LaneEndBackwardRoutings[laneRoutingIndex], startNode, true, ref netManager.m_nodes.m_buffer[startNode], 0, false); + } + + if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0) { + ushort endNode = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; + uint laneRoutingIndex = routingManager.GetLaneEndRoutingIndex(candidateItem.m_laneID, false); + this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.SegmentRoutings[candidateItem.m_position.m_segment], routingManager.LaneEndBackwardRoutings[laneRoutingIndex], endNode, false, ref netManager.m_nodes.m_buffer[endNode], 255, false); + } + + // handle special nodes (e.g. bus stops) + int num6 = 0; + ushort specialNodeId = netManager.m_lanes.m_buffer[candidateItem.m_laneID].m_nodes; + if (specialNodeId != 0) { + ushort startNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; + ushort endNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; + bool nodesDisabled = ((netManager.m_nodes.m_buffer[startNode2].m_flags | netManager.m_nodes.m_buffer[endNode2].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None; + while (specialNodeId != 0) { + NetInfo.Direction direction = NetInfo.Direction.None; + byte laneOffset = netManager.m_nodes.m_buffer[specialNodeId].m_laneOffset; + if (laneOffset <= candidateItem.m_position.m_offset) { + direction |= NetInfo.Direction.Forward; + } + if (laneOffset >= candidateItem.m_position.m_offset) { + direction |= NetInfo.Direction.Backward; + } + if ((byte)(candidateItem.m_direction & direction) != 0 && (!nodesDisabled || (netManager.m_nodes.m_buffer[specialNodeId].m_flags & NetNode.Flags.Disabled) != NetNode.Flags.None)) { #if DEBUGNEWPF && DEBUG - if (debug && (m_conf.Debug.NodeId <= 0 || specialNodeId == m_conf.Debug.NodeId)) { - Log._Debug($"CustomPathFind.PathFindImplementation: Handling special node for path unit {unit}, type {queueItem.vehicleType}:\n" + - $"\tcandidateItem.m_position.m_segment={candidateItem.m_position.m_segment}\n" + - $"\tcandidateItem.m_position.m_lane={candidateItem.m_position.m_lane}\n" + - $"\tcandidateItem.m_laneID={candidateItem.m_laneID}\n" + - $"\tspecialNodeId={specialNodeId}\n" + - $"\tstartNode2={startNode2}\n" + - $"\tendNode2={endNode2}\n" - ); - } -#endif - this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.SegmentRoutings[candidateItem.m_position.m_segment], routingManager.LaneEndBackwardRoutings[0], specialNodeId, false, ref netManager.m_nodes.m_buffer[specialNodeId], laneOffset, true); - } - specialNodeId = netManager.m_nodes.m_buffer[specialNodeId].m_nextLaneNode; - if (++num6 >= NetManager.MAX_NODE_COUNT) { - Log.Warning("Special loop: Too many iterations"); - break; - } - } - } - } - } - - if (!canFindPath) { - // we could not find a path - PathUnits.m_buffer[(int)unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; + if (debug && (m_conf.Debug.NodeId <= 0 || specialNodeId == m_conf.Debug.NodeId)) { + Log._Debug($"CustomPathFind.PathFindImplementation: Handling special node for path unit {unit}, type {queueItem.vehicleType}:\n" + + $"\tcandidateItem.m_position.m_segment={candidateItem.m_position.m_segment}\n" + + $"\tcandidateItem.m_position.m_lane={candidateItem.m_position.m_lane}\n" + + $"\tcandidateItem.m_laneID={candidateItem.m_laneID}\n" + + $"\tspecialNodeId={specialNodeId}\n" + + $"\tstartNode2={startNode2}\n" + + $"\tendNode2={endNode2}\n" + ); + } +#endif + this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.SegmentRoutings[candidateItem.m_position.m_segment], routingManager.LaneEndBackwardRoutings[0], specialNodeId, false, ref netManager.m_nodes.m_buffer[specialNodeId], laneOffset, true); + } + specialNodeId = netManager.m_nodes.m_buffer[specialNodeId].m_nextLaneNode; + if (++num6 >= NetManager.MAX_NODE_COUNT) { + Log.Warning("Special loop: Too many iterations"); + break; + } + } + } + } + } + + if (!canFindPath) { + // we could not find a path + PathUnits.m_buffer[(int)unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; #if DEBUG - ++m_failedPathFinds; - -#if DEBUGNEWPF - if (debug) { - Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- path-finding failed during process"); - string reachableBuf = ""; - string unreachableBuf = ""; - foreach (KeyValuePair> e in m_debugPositions) { - string buf = $"{e.Key} -> {e.Value.CollectionToString()}\n"; - if (e.Value.Count <= 0) { - unreachableBuf += buf; - } else { - reachableBuf += buf; - } - } - Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Reachability graph for unit {unit}:\n== REACHABLE ==\n" + reachableBuf + "\n== UNREACHABLE ==\n" + unreachableBuf); - } -#endif -#endif - //CustomPathManager._instance.ResetQueueItem(unit); - - return; - } - // we could calculate a valid path - - float duration = (this.m_laneTypes != NetInfo.LaneType.Pedestrian) ? finalBufferItem.m_duration : finalBufferItem.m_methodDistance; - this.PathUnits.m_buffer[unit].m_length = duration; - this.PathUnits.m_buffer[unit].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // NON-STOCK CODE - this.PathUnits.m_buffer[unit].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // NON-STOCK CODE + ++m_failedPathFinds; + +#if DEBUGNEWPF + if (debug) { + Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- path-finding failed during process"); + string reachableBuf = ""; + string unreachableBuf = ""; + foreach (KeyValuePair> e in m_debugPositions) { + string buf = $"{e.Key} -> {e.Value.CollectionToString()}\n"; + if (e.Value.Count <= 0) { + unreachableBuf += buf; + } else { + reachableBuf += buf; + } + } + Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Reachability graph for unit {unit}:\n== REACHABLE ==\n" + reachableBuf + "\n== UNREACHABLE ==\n" + unreachableBuf); + } +#endif +#endif + //CustomPathManager._instance.ResetQueueItem(unit); + + return; + } + // we could calculate a valid path + + float duration = (this.m_laneTypes != NetInfo.LaneType.Pedestrian) ? finalBufferItem.m_duration : finalBufferItem.m_methodDistance; + this.PathUnits.m_buffer[unit].m_length = duration; + this.PathUnits.m_buffer[unit].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // NON-STOCK CODE + this.PathUnits.m_buffer[unit].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // NON-STOCK CODE #if DEBUG - /*if (_conf.Debug.Switches[4]) - Log._Debug($"Lane/Vehicle types of path unit {unit}: {finalBufferItem.m_lanesUsed} / {finalBufferItem.m_vehiclesUsed}");*/ -#endif - uint currentPathUnitId = unit; - int currentItemPositionCount = 0; - int sumOfPositionCounts = 0; - PathUnit.Position currentPosition = finalBufferItem.m_position; - if ((currentPosition.m_segment != bufferItemEndA.m_position.m_segment || currentPosition.m_lane != bufferItemEndA.m_position.m_lane || currentPosition.m_offset != bufferItemEndA.m_position.m_offset) && - (currentPosition.m_segment != bufferItemEndB.m_position.m_segment || currentPosition.m_lane != bufferItemEndB.m_position.m_lane || currentPosition.m_offset != bufferItemEndB.m_position.m_offset)) { - // the found starting position differs from the desired end position - if (startOffset != currentPosition.m_offset) { - // the offsets differ: copy the found starting position and modify the offset to fit the desired offset - PathUnit.Position position2 = currentPosition; - position2.m_offset = startOffset; - this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, position2); - // now we have: [desired starting position] - } - // add the found starting position to the path unit - this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); - currentPosition = this.m_laneTarget[finalBufferItem.m_laneID]; // go to the next path position - - // now we have either [desired starting position, found starting position] or [found starting position], depending on if the found starting position matched the desired - } - - // beginning with the starting position, going to the target position: assemble the path units - for (int k = 0; k < 262144; ++k) { - //pfCurrentState = 6; - this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); // add the next path position to the current unit - - if ((currentPosition.m_segment == bufferItemEndA.m_position.m_segment && currentPosition.m_lane == bufferItemEndA.m_position.m_lane && currentPosition.m_offset == bufferItemEndA.m_position.m_offset) || - (currentPosition.m_segment == bufferItemEndB.m_position.m_segment && currentPosition.m_lane == bufferItemEndB.m_position.m_lane && currentPosition.m_offset == bufferItemEndB.m_position.m_offset)) { - // we have reached the end position - - this.PathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; - sumOfPositionCounts += currentItemPositionCount; // add position count of last unit to sum - if (sumOfPositionCounts != 0) { - // for each path unit from start to target: calculate length (distance) to target - currentPathUnitId = this.PathUnits.m_buffer[unit].m_nextPathUnit; // (we do not need to calculate the length for the starting unit since this is done before; it's the total path length) - currentItemPositionCount = (int)this.PathUnits.m_buffer[unit].m_positionCount; - int totalIter = 0; - while (currentPathUnitId != 0u) { - this.PathUnits.m_buffer[currentPathUnitId].m_length = duration * (float)(sumOfPositionCounts - currentItemPositionCount) / (float)sumOfPositionCounts; - currentItemPositionCount += (int)this.PathUnits.m_buffer[currentPathUnitId].m_positionCount; - currentPathUnitId = this.PathUnits.m_buffer[currentPathUnitId].m_nextPathUnit; - if (++totalIter >= 262144) { + /*if (_conf.Debug.Switches[4]) + Log._Debug($"Lane/Vehicle types of path unit {unit}: {finalBufferItem.m_lanesUsed} / {finalBufferItem.m_vehiclesUsed}");*/ +#endif + uint currentPathUnitId = unit; + int currentItemPositionCount = 0; + int sumOfPositionCounts = 0; + PathUnit.Position currentPosition = finalBufferItem.m_position; + if ((currentPosition.m_segment != bufferItemEndA.m_position.m_segment || currentPosition.m_lane != bufferItemEndA.m_position.m_lane || currentPosition.m_offset != bufferItemEndA.m_position.m_offset) && + (currentPosition.m_segment != bufferItemEndB.m_position.m_segment || currentPosition.m_lane != bufferItemEndB.m_position.m_lane || currentPosition.m_offset != bufferItemEndB.m_position.m_offset)) { + // the found starting position differs from the desired end position + if (startOffset != currentPosition.m_offset) { + // the offsets differ: copy the found starting position and modify the offset to fit the desired offset + PathUnit.Position position2 = currentPosition; + position2.m_offset = startOffset; + this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, position2); + // now we have: [desired starting position] + } + // add the found starting position to the path unit + this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); + currentPosition = this.m_laneTarget[finalBufferItem.m_laneID]; // go to the next path position + + // now we have either [desired starting position, found starting position] or [found starting position], depending on if the found starting position matched the desired + } + + // beginning with the starting position, going to the target position: assemble the path units + for (int k = 0; k < 262144; ++k) { + //pfCurrentState = 6; + this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); // add the next path position to the current unit + + if ((currentPosition.m_segment == bufferItemEndA.m_position.m_segment && currentPosition.m_lane == bufferItemEndA.m_position.m_lane && currentPosition.m_offset == bufferItemEndA.m_position.m_offset) || + (currentPosition.m_segment == bufferItemEndB.m_position.m_segment && currentPosition.m_lane == bufferItemEndB.m_position.m_lane && currentPosition.m_offset == bufferItemEndB.m_position.m_offset)) { + // we have reached the end position + + this.PathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; + sumOfPositionCounts += currentItemPositionCount; // add position count of last unit to sum + if (sumOfPositionCounts != 0) { + // for each path unit from start to target: calculate length (distance) to target + currentPathUnitId = this.PathUnits.m_buffer[unit].m_nextPathUnit; // (we do not need to calculate the length for the starting unit since this is done before; it's the total path length) + currentItemPositionCount = (int)this.PathUnits.m_buffer[unit].m_positionCount; + int totalIter = 0; + while (currentPathUnitId != 0u) { + this.PathUnits.m_buffer[currentPathUnitId].m_length = duration * (float)(sumOfPositionCounts - currentItemPositionCount) / (float)sumOfPositionCounts; + currentItemPositionCount += (int)this.PathUnits.m_buffer[currentPathUnitId].m_positionCount; + currentPathUnitId = this.PathUnits.m_buffer[currentPathUnitId].m_nextPathUnit; + if (++totalIter >= 262144) { #if DEBUG - Log.Error("THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: PathFindImplementation: Invalid list detected."); + Log.Error("THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: PathFindImplementation: Invalid list detected."); #endif - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } #if DEBUG - //Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: Path found (pfCurrentState={pfCurrentState}) for unit {unit}"); + //Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: Path found (pfCurrentState={pfCurrentState}) for unit {unit}"); #endif - PathUnits.m_buffer[(int)unit].m_pathFindFlags |= PathUnit.FLAG_READY; // Path found + PathUnits.m_buffer[(int)unit].m_pathFindFlags |= PathUnit.FLAG_READY; // Path found #if DEBUG - ++m_succeededPathFinds; + ++m_succeededPathFinds; #if DEBUGNEWPF - if (debug) - Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Path-find succeeded for unit {unit}"); + if (debug) + Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Path-find succeeded for unit {unit}"); #endif #endif - //CustomPathManager._instance.ResetQueueItem(unit); + //CustomPathManager._instance.ResetQueueItem(unit); - return; - } + return; + } - // We have not reached the target position yet - if (currentItemPositionCount == 12) { - // the current path unit is full, we need a new one - uint createdPathUnitId; - try { - Monitor.Enter(_bufferLock); - if (!this.PathUnits.CreateItem(out createdPathUnitId, ref this.m_pathRandomizer)) { - // we failed to create a new path unit, thus the path-finding also failed - PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; + // We have not reached the target position yet + if (currentItemPositionCount == 12) { + // the current path unit is full, we need a new one + uint createdPathUnitId; + try { + Monitor.Enter(_bufferLock); + if (!this.PathUnits.CreateItem(out createdPathUnitId, ref this.m_pathRandomizer)) { + // we failed to create a new path unit, thus the path-finding also failed + PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; #if DEBUG - ++m_failedPathFinds; - -#if DEBUGNEWPF - if (debug) - Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- Could not create path unit"); -#endif -#endif - //CustomPathManager._instance.ResetQueueItem(unit); - return; - } - this.PathUnits.m_buffer[createdPathUnitId] = this.PathUnits.m_buffer[(int)currentPathUnitId]; - this.PathUnits.m_buffer[createdPathUnitId].m_referenceCount = 1; - this.PathUnits.m_buffer[createdPathUnitId].m_pathFindFlags = PathUnit.FLAG_READY; - this.PathUnits.m_buffer[currentPathUnitId].m_nextPathUnit = createdPathUnitId; - this.PathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; - this.PathUnits.m_buffer[currentPathUnitId].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // NON-STOCK CODE (this is not accurate!) - this.PathUnits.m_buffer[currentPathUnitId].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // NON-STOCK CODE (this is not accurate!) - sumOfPositionCounts += currentItemPositionCount; - Singleton.instance.m_pathUnitCount = (int)(this.PathUnits.ItemCount() - 1u); - } catch (Exception e) { - Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindImplementation Error: {e.ToString()}"); - break; - } finally { - Monitor.Exit(this._bufferLock); - } - currentPathUnitId = createdPathUnitId; - currentItemPositionCount = 0; - } - - uint laneID = PathManager.GetLaneID(currentPosition); + ++m_failedPathFinds; + +#if DEBUGNEWPF + if (debug) + Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- Could not create path unit"); +#endif +#endif + //CustomPathManager._instance.ResetQueueItem(unit); + return; + } + this.PathUnits.m_buffer[createdPathUnitId] = this.PathUnits.m_buffer[(int)currentPathUnitId]; + this.PathUnits.m_buffer[createdPathUnitId].m_referenceCount = 1; + this.PathUnits.m_buffer[createdPathUnitId].m_pathFindFlags = PathUnit.FLAG_READY; + this.PathUnits.m_buffer[currentPathUnitId].m_nextPathUnit = createdPathUnitId; + this.PathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; + this.PathUnits.m_buffer[currentPathUnitId].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // NON-STOCK CODE (this is not accurate!) + this.PathUnits.m_buffer[currentPathUnitId].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // NON-STOCK CODE (this is not accurate!) + sumOfPositionCounts += currentItemPositionCount; + Singleton.instance.m_pathUnitCount = (int)(this.PathUnits.ItemCount() - 1u); + } catch (Exception e) { + Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindImplementation Error: {e.ToString()}"); + break; + } finally { + Monitor.Exit(this._bufferLock); + } + currentPathUnitId = createdPathUnitId; + currentItemPositionCount = 0; + } + + uint laneID = PathManager.GetLaneID(currentPosition); #if PFTRAFFICSTATS // NON-STOCK CODE START #if MEASUREDENSITY @@ -682,971 +687,971 @@ protected void PathFindImplementation(uint unit, ref PathUnit data) { } // NON-STOCK CODE END #endif - currentPosition = this.m_laneTarget[laneID]; - } - PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; + currentPosition = this.m_laneTarget[laneID]; + } + PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; #if DEBUG - ++m_failedPathFinds; + ++m_failedPathFinds; #if DEBUGNEWPF - if (debug) - Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- internal error: for loop break"); + if (debug) + Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- internal error: for loop break"); #endif #endif - //CustomPathManager._instance.ResetQueueItem(unit); + //CustomPathManager._instance.ResetQueueItem(unit); #if DEBUG - //Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: Cannot find path (pfCurrentState={pfCurrentState}) for unit {unit}"); + //Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: Cannot find path (pfCurrentState={pfCurrentState}) for unit {unit}"); #endif - } + } - // be aware: - // (1) path-finding works from target to start. the "next" segment is always the previous and the "previous" segment is always the next segment on the path! - // (2) when I use the term "lane index from outer" this means outer right lane for right-hand traffic systems and outer-left lane for left-hand traffic systems. + // be aware: + // (1) path-finding works from target to start. the "next" segment is always the previous and the "previous" segment is always the next segment on the path! + // (2) when I use the term "lane index from outer" this means outer right lane for right-hand traffic systems and outer-left lane for left-hand traffic systems. - // 1 - private void ProcessItemMain(uint unitId, BufferItem item, ref NetSegment prevSegment, SegmentRoutingData prevSegmentRouting, LaneEndRoutingData prevLaneEndRouting, ushort nextNodeId, bool nextIsStartNode, ref NetNode nextNode, byte connectOffset, bool isMiddle) { + // 1 + private void ProcessItemMain(uint unitId, BufferItem item, ref NetSegment prevSegment, SegmentRoutingData prevSegmentRouting, LaneEndRoutingData prevLaneEndRouting, ushort nextNodeId, bool nextIsStartNode, ref NetNode nextNode, byte connectOffset, bool isMiddle) { #if DEBUGNEWPF && DEBUG - bool debug = this.m_debug && (m_conf.Debug.NodeId <= 0 || nextNodeId == m_conf.Debug.NodeId); - bool debugPed = debug && m_conf.Debug.Switches[12]; - if (debug) { - if (! m_debugPositions.ContainsKey(item.m_position.m_segment)) { - m_debugPositions[item.m_position.m_segment] = new List(); - } - } + bool debug = this.m_debug && (m_conf.Debug.NodeId <= 0 || nextNodeId == m_conf.Debug.NodeId); + bool debugPed = debug && m_conf.Debug.Switches[12]; + if (debug) { + if (! m_debugPositions.ContainsKey(item.m_position.m_segment)) { + m_debugPositions[item.m_position.m_segment] = new List(); + } + } #else bool debug = false; bool debugPed = false; #endif - //Log.Message($"THREAD #{Thread.CurrentThread.ManagedThreadId} Path finder: " + this._pathFindIndex + " vehicle types: " + this._vehicleTypes); + //Log.Message($"THREAD #{Thread.CurrentThread.ManagedThreadId} Path finder: " + this._pathFindIndex + " vehicle types: " + this._vehicleTypes); #if DEBUGNEWPF && DEBUG - //bool debug = isTransportVehicle && isMiddle && item.m_position.m_segment == 13550; - List logBuf = null; - if (debug) - logBuf = new List(); -#endif - - NetManager netManager = Singleton.instance; - bool prevIsPedestrianLane = false; - //bool prevIsBusLane = false; // non-stock - bool prevIsBicycleLane = false; - bool prevIsCenterPlatform = false; - bool prevIsElevated = false; - bool prevIsCarLane = false; - int prevRelSimilarLaneIndex = 0; // inner/outer similar index - //int prevInnerSimilarLaneIndex = 0; // similar index, starting with 0 at leftmost lane in right hand traffic - int prevOuterSimilarLaneIndex = 0; // similar index, starting with 0 at rightmost lane in right hand traffic - NetInfo prevSegmentInfo = prevSegment.Info; - NetInfo.Lane prevLaneInfo = null; - byte prevSimilarLaneCount = 0; - if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { - prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; - prevIsPedestrianLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian); - prevIsBicycleLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (prevLaneInfo.m_vehicleType & this.m_vehicleTypes) == VehicleInfo.VehicleType.Bicycle); - prevIsCarLane = (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None; - //prevIsBusLane = (prevLane.m_laneType == NetInfo.LaneType.TransportVehicle && (prevLane.m_vehicleType & this._vehicleTypes & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None); - prevIsCenterPlatform = prevLaneInfo.m_centerPlatform; - prevIsElevated = prevLaneInfo.m_elevated; - prevSimilarLaneCount = (byte)prevLaneInfo.m_similarLaneCount; - //prevInnerSimilarLaneIndex = RoutingManager.Instance.CalcInnerSimilarLaneIndex(prevLaneInfo); - prevOuterSimilarLaneIndex = RoutingManager.Instance.CalcOuterSimilarLaneIndex(prevLaneInfo); - if ((byte)(prevLaneInfo.m_finalDirection & NetInfo.Direction.Forward) != 0) { - prevRelSimilarLaneIndex = prevLaneInfo.m_similarLaneIndex; - } else { - prevRelSimilarLaneIndex = prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1; - } - } - int firstPrevSimilarLaneIndexFromInner = prevRelSimilarLaneIndex; - ushort prevSegmentId = item.m_position.m_segment; - if (isMiddle) { - for (int i = 0; i < 8; ++i) { - ushort nextSegmentId = nextNode.GetSegment(i); - if (nextSegmentId <= 0) - continue; - -#if DEBUGNEWPF - if (debug) { - FlushMainLog(logBuf, unitId); - } -#endif - - this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[(int)nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsPedestrianLane, prevIsPedestrianLane, isMiddle); - } - } else if (prevIsPedestrianLane) { - bool allowPedSwitch = (this.m_laneTypes & NetInfo.LaneType.Pedestrian) != 0; - if (!prevIsElevated) { - // explore pedestrian lanes - int prevLaneIndex = (int)item.m_position.m_lane; - if (nextNode.Info.m_class.m_service != ItemClass.Service.Beautification) { - if (allowPedSwitch) { // NON-STOCK CODE - bool canCrossStreet = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; - bool isOnCenterPlatform = prevIsCenterPlatform && (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Junction)) == NetNode.Flags.None; - ushort nextLeftSegment = prevSegmentId; - ushort nextRightSegment = prevSegmentId; - int leftLaneIndex; - int rightLaneIndex; - uint leftLaneId; - uint rightLaneId; - prevSegment.GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, prevLaneIndex, isOnCenterPlatform, out leftLaneIndex, out rightLaneIndex, out leftLaneId, out rightLaneId); - if (leftLaneId == 0u || rightLaneId == 0u) { - ushort leftSegment; - ushort rightSegment; - prevSegment.GetLeftAndRightSegments(nextNodeId, out leftSegment, out rightSegment); - int numIter = 0; - while (leftSegment != 0 && leftSegment != prevSegmentId && leftLaneId == 0u) { - int someLeftLaneIndex; - int someRightLaneIndex; - uint someLeftLaneId; - uint someRightLaneId; - netManager.m_segments.m_buffer[(int)leftSegment].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); - if (someRightLaneId != 0u) { - nextLeftSegment = leftSegment; - leftLaneIndex = someRightLaneIndex; - leftLaneId = someRightLaneId; - } else { - leftSegment = netManager.m_segments.m_buffer[(int)leftSegment].GetLeftSegment(nextNodeId); - } - if (++numIter == 8) { - break; - } - } - numIter = 0; - while (rightSegment != 0 && rightSegment != prevSegmentId && rightLaneId == 0u) { - int someLeftLaneIndex; - int someRightLaneIndex; - uint someLeftLaneId; - uint someRightLaneId; - netManager.m_segments.m_buffer[(int)rightSegment].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); - if (someLeftLaneId != 0u) { - nextRightSegment = rightSegment; - rightLaneIndex = someLeftLaneIndex; - rightLaneId = someLeftLaneId; - } else { - rightSegment = netManager.m_segments.m_buffer[(int)rightSegment].GetRightSegment(nextNodeId); - } - if (++numIter == 8) { - break; - } - } - } - if (leftLaneId != 0u && (nextLeftSegment != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { -#if DEBUGNEWPF - if (debugPed) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring left segment\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" + - "\t" + $"nextLeftSegment={nextLeftSegment}\n" + - "\t" + $"leftLaneId={leftLaneId}\n" + - "\t" + $"mayCrossStreet={canCrossStreet}\n" + - "\t" + $"isOnCenterPlatform={isOnCenterPlatform}\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - this.ProcessItemPedBicycle(debugPed, item, nextNodeId, nextLeftSegment, ref prevSegment, ref netManager.m_segments.m_buffer[(int)nextLeftSegment], connectOffset, connectOffset, leftLaneIndex, leftLaneId); // ped - } - if (rightLaneId != 0u && rightLaneId != leftLaneId && (nextRightSegment != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { -#if DEBUGNEWPF - if (debugPed) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring right segment\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" + - "\t" + $"nextRightSegment={nextRightSegment}\n" + - "\t" + $"rightLaneId={rightLaneId}\n" + - "\t" + $"mayCrossStreet={canCrossStreet}\n" + - "\t" + $"isOnCenterPlatform={isOnCenterPlatform}\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - this.ProcessItemPedBicycle(debugPed, item, nextNodeId, nextRightSegment, ref prevSegment, ref netManager.m_segments.m_buffer[(int)nextRightSegment], connectOffset, connectOffset, rightLaneIndex, rightLaneId); // ped - } - } - - // switch from bicycle lane to pedestrian lane - int nextLaneIndex; - uint nextLaneId; - if ((this.m_vehicleTypes & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None && - prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Bicycle, out nextLaneIndex, out nextLaneId)) { -#if DEBUGNEWPF - if (debugPed) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring bicycle switch\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" + - "\t" + $"nextLaneIndex={nextLaneIndex}\n" + - "\t" + $"nextLaneId={nextLaneId}\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - this.ProcessItemPedBicycle(debugPed, item, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, connectOffset, connectOffset, nextLaneIndex, nextLaneId); // bicycle - } - } else { - // we are going from pedestrian lane to a beautification node - - for (int j = 0; j < 8; ++j) { - ushort nextSegmentId = nextNode.GetSegment(j); - if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { -#if DEBUGNEWPF - if (debug) { - FlushMainLog(logBuf, unitId); - } -#endif - - this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[(int)nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, false, true, isMiddle); - } - } - } - - // NON-STOCK CODE START - // switch from vehicle to pedestrian lane (parking) - bool parkingAllowed = true; - if (Options.parkingAI) { - if (queueItem.vehicleType == ExtVehicleType.PassengerCar) { - if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - // if pocket cars are prohibited, a citizen may only park their car once per path - parkingAllowed = false; - } else if ((item.m_lanesUsed & NetInfo.LaneType.PublicTransport) == NetInfo.LaneType.None) { - // if the citizen is walking to their target (= no public transport used), the passenger car must be parked in the very last moment - parkingAllowed = item.m_laneID == m_endLaneA || item.m_laneID == m_endLaneB; - /*if (_conf.Debug.Switches[4]) { - Log._Debug($"Path unit {unitId}: public transport has not been used. "); - }*/ - } - } - } - - if (parkingAllowed) { - // NON-STOCK CODE END - NetInfo.LaneType laneType = this.m_laneTypes & ~NetInfo.LaneType.Pedestrian; - VehicleInfo.VehicleType vehicleType = this.m_vehicleTypes & ~VehicleInfo.VehicleType.Bicycle; - if ((byte)(item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { - laneType &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - int nextLaneIndex2; - uint nextlaneId2; - if (laneType != NetInfo.LaneType.None && - vehicleType != VehicleInfo.VehicleType.None && - prevSegment.GetClosestLane(prevLaneIndex, laneType, vehicleType, out nextLaneIndex2, out nextlaneId2)) { - NetInfo.Lane lane5 = prevSegmentInfo.m_lanes[nextLaneIndex2]; - byte connectOffset2; - if ((prevSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None == ((byte)(lane5.m_finalDirection & NetInfo.Direction.Backward) != 0)) { - connectOffset2 = 1; - } else { - connectOffset2 = 254; - } - - CustomPathFind.BufferItem item2 = item; - if (this.m_randomParking) { - item2.m_comparisonValue += (float)this.m_pathRandomizer.Int32(300u) / this.m_maxLength; - } -#if DEBUGNEWPF - if (debugPed) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring parking switch\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" + - "\t" + $"nextLaneIndex2={nextLaneIndex2}\n" + - "\t" + $"nextlaneId2={nextlaneId2}\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - this.ProcessItemPedBicycle(debugPed, item2, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, connectOffset2, 128, nextLaneIndex2, nextlaneId2); // ped - } - } - } - } else { - // we are going to a non-pedestrian lane - - bool allowPedestrian = (byte)(this.m_laneTypes & NetInfo.LaneType.Pedestrian) != 0; // allow pedestrian switching to vehicle? - bool nextIsBeautificationNode = nextNode.Info.m_class.m_service == ItemClass.Service.Beautification; - bool allowBicycle = false; // is true if cim may switch from a pedestrian lane to a bike lane - byte parkingConnectOffset = 0; - if (allowPedestrian) { - if (prevIsBicycleLane) { - // we are going to a bicycle lane - parkingConnectOffset = connectOffset; - allowBicycle = nextIsBeautificationNode; - } else if (this.m_vehicleLane != 0u) { - // there is a parked vehicle position - if (this.m_vehicleLane != item.m_laneID) { - // we have not reached the parked vehicle yet - allowPedestrian = false; - } else { - // pedestrian switches to parked vehicle - parkingConnectOffset = this.m_vehicleOffset; - } - } else if (this.m_stablePath) { - // enter a bus - parkingConnectOffset = 128; - } else { - // pocket car spawning - if (Options.parkingAI && - queueItem.vehicleType == ExtVehicleType.PassengerCar && - (queueItem.pathType == ExtPathType.WalkingOnly || (queueItem.pathType == ExtPathType.DrivingOnly && item.m_position.m_segment != m_startSegmentA && item.m_position.m_segment != m_startSegmentB))) { - allowPedestrian = false; - } else { - parkingConnectOffset = (byte)this.m_pathRandomizer.UInt32(1u, 254u); - } - } - } - - if ((this.m_vehicleTypes & (VehicleInfo.VehicleType.Ferry /* | VehicleInfo.VehicleType.Monorail*/)) != VehicleInfo.VehicleType.None) { - // monorail / ferry - - for (int k = 0; k < 8; k++) { - ushort nextSegmentId = nextNode.GetSegment(k); - if (nextSegmentId == 0 || nextSegmentId == prevSegmentId) { - continue; - } - - this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle, isMiddle); - } - - if ((nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None /*&& + //bool debug = isTransportVehicle && isMiddle && item.m_position.m_segment == 13550; + List logBuf = null; + if (debug) + logBuf = new List(); +#endif + + NetManager netManager = Singleton.instance; + bool prevIsPedestrianLane = false; + //bool prevIsBusLane = false; // non-stock + bool prevIsBicycleLane = false; + bool prevIsCenterPlatform = false; + bool prevIsElevated = false; + bool prevIsCarLane = false; + int prevRelSimilarLaneIndex = 0; // inner/outer similar index + //int prevInnerSimilarLaneIndex = 0; // similar index, starting with 0 at leftmost lane in right hand traffic + int prevOuterSimilarLaneIndex = 0; // similar index, starting with 0 at rightmost lane in right hand traffic + NetInfo prevSegmentInfo = prevSegment.Info; + NetInfo.Lane prevLaneInfo = null; + byte prevSimilarLaneCount = 0; + if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { + prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; + prevIsPedestrianLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian); + prevIsBicycleLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (prevLaneInfo.m_vehicleType & this.m_vehicleTypes) == VehicleInfo.VehicleType.Bicycle); + prevIsCarLane = (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None; + //prevIsBusLane = (prevLane.m_laneType == NetInfo.LaneType.TransportVehicle && (prevLane.m_vehicleType & this._vehicleTypes & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None); + prevIsCenterPlatform = prevLaneInfo.m_centerPlatform; + prevIsElevated = prevLaneInfo.m_elevated; + prevSimilarLaneCount = (byte)prevLaneInfo.m_similarLaneCount; + //prevInnerSimilarLaneIndex = RoutingManager.Instance.CalcInnerSimilarLaneIndex(prevLaneInfo); + prevOuterSimilarLaneIndex = RoutingManager.Instance.CalcOuterSimilarLaneIndex(prevLaneInfo); + if ((byte)(prevLaneInfo.m_finalDirection & NetInfo.Direction.Forward) != 0) { + prevRelSimilarLaneIndex = prevLaneInfo.m_similarLaneIndex; + } else { + prevRelSimilarLaneIndex = prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1; + } + } + int firstPrevSimilarLaneIndexFromInner = prevRelSimilarLaneIndex; + ushort prevSegmentId = item.m_position.m_segment; + if (isMiddle) { + for (int i = 0; i < 8; ++i) { + ushort nextSegmentId = nextNode.GetSegment(i); + if (nextSegmentId <= 0) + continue; + +#if DEBUGNEWPF + if (debug) { + FlushMainLog(logBuf, unitId); + } +#endif + + this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[(int)nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsPedestrianLane, prevIsPedestrianLane, isMiddle); + } + } else if (prevIsPedestrianLane) { + bool allowPedSwitch = (this.m_laneTypes & NetInfo.LaneType.Pedestrian) != 0; + if (!prevIsElevated) { + // explore pedestrian lanes + int prevLaneIndex = (int)item.m_position.m_lane; + if (nextNode.Info.m_class.m_service != ItemClass.Service.Beautification) { + if (allowPedSwitch) { // NON-STOCK CODE + bool canCrossStreet = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; + bool isOnCenterPlatform = prevIsCenterPlatform && (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Junction)) == NetNode.Flags.None; + ushort nextLeftSegment = prevSegmentId; + ushort nextRightSegment = prevSegmentId; + int leftLaneIndex; + int rightLaneIndex; + uint leftLaneId; + uint rightLaneId; + prevSegment.GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, prevLaneIndex, isOnCenterPlatform, out leftLaneIndex, out rightLaneIndex, out leftLaneId, out rightLaneId); + if (leftLaneId == 0u || rightLaneId == 0u) { + ushort leftSegment; + ushort rightSegment; + prevSegment.GetLeftAndRightSegments(nextNodeId, out leftSegment, out rightSegment); + int numIter = 0; + while (leftSegment != 0 && leftSegment != prevSegmentId && leftLaneId == 0u) { + int someLeftLaneIndex; + int someRightLaneIndex; + uint someLeftLaneId; + uint someRightLaneId; + netManager.m_segments.m_buffer[(int)leftSegment].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); + if (someRightLaneId != 0u) { + nextLeftSegment = leftSegment; + leftLaneIndex = someRightLaneIndex; + leftLaneId = someRightLaneId; + } else { + leftSegment = netManager.m_segments.m_buffer[(int)leftSegment].GetLeftSegment(nextNodeId); + } + if (++numIter == 8) { + break; + } + } + numIter = 0; + while (rightSegment != 0 && rightSegment != prevSegmentId && rightLaneId == 0u) { + int someLeftLaneIndex; + int someRightLaneIndex; + uint someLeftLaneId; + uint someRightLaneId; + netManager.m_segments.m_buffer[(int)rightSegment].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); + if (someLeftLaneId != 0u) { + nextRightSegment = rightSegment; + rightLaneIndex = someLeftLaneIndex; + rightLaneId = someLeftLaneId; + } else { + rightSegment = netManager.m_segments.m_buffer[(int)rightSegment].GetRightSegment(nextNodeId); + } + if (++numIter == 8) { + break; + } + } + } + if (leftLaneId != 0u && (nextLeftSegment != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { +#if DEBUGNEWPF + if (debugPed) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring left segment\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + + "\t" + $"nextLeftSegment={nextLeftSegment}\n" + + "\t" + $"leftLaneId={leftLaneId}\n" + + "\t" + $"mayCrossStreet={canCrossStreet}\n" + + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + this.ProcessItemPedBicycle(debugPed, item, nextNodeId, nextLeftSegment, ref prevSegment, ref netManager.m_segments.m_buffer[(int)nextLeftSegment], connectOffset, connectOffset, leftLaneIndex, leftLaneId); // ped + } + if (rightLaneId != 0u && rightLaneId != leftLaneId && (nextRightSegment != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { +#if DEBUGNEWPF + if (debugPed) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring right segment\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + + "\t" + $"nextRightSegment={nextRightSegment}\n" + + "\t" + $"rightLaneId={rightLaneId}\n" + + "\t" + $"mayCrossStreet={canCrossStreet}\n" + + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + this.ProcessItemPedBicycle(debugPed, item, nextNodeId, nextRightSegment, ref prevSegment, ref netManager.m_segments.m_buffer[(int)nextRightSegment], connectOffset, connectOffset, rightLaneIndex, rightLaneId); // ped + } + } + + // switch from bicycle lane to pedestrian lane + int nextLaneIndex; + uint nextLaneId; + if ((this.m_vehicleTypes & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None && + prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Bicycle, out nextLaneIndex, out nextLaneId)) { +#if DEBUGNEWPF + if (debugPed) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring bicycle switch\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + + "\t" + $"nextLaneId={nextLaneId}\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + this.ProcessItemPedBicycle(debugPed, item, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, connectOffset, connectOffset, nextLaneIndex, nextLaneId); // bicycle + } + } else { + // we are going from pedestrian lane to a beautification node + + for (int j = 0; j < 8; ++j) { + ushort nextSegmentId = nextNode.GetSegment(j); + if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { +#if DEBUGNEWPF + if (debug) { + FlushMainLog(logBuf, unitId); + } +#endif + + this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[(int)nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, false, true, isMiddle); + } + } + } + + // NON-STOCK CODE START + // switch from vehicle to pedestrian lane (parking) + bool parkingAllowed = true; + if (Options.parkingAI) { + if (queueItem.vehicleType == ExtVehicleType.PassengerCar) { + if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + // if pocket cars are prohibited, a citizen may only park their car once per path + parkingAllowed = false; + } else if ((item.m_lanesUsed & NetInfo.LaneType.PublicTransport) == NetInfo.LaneType.None) { + // if the citizen is walking to their target (= no public transport used), the passenger car must be parked in the very last moment + parkingAllowed = item.m_laneID == m_endLaneA || item.m_laneID == m_endLaneB; + /*if (_conf.Debug.Switches[4]) { + Log._Debug($"Path unit {unitId}: public transport has not been used. "); + }*/ + } + } + } + + if (parkingAllowed) { + // NON-STOCK CODE END + NetInfo.LaneType laneType = this.m_laneTypes & ~NetInfo.LaneType.Pedestrian; + VehicleInfo.VehicleType vehicleType = this.m_vehicleTypes & ~VehicleInfo.VehicleType.Bicycle; + if ((byte)(item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { + laneType &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + int nextLaneIndex2; + uint nextlaneId2; + if (laneType != NetInfo.LaneType.None && + vehicleType != VehicleInfo.VehicleType.None && + prevSegment.GetClosestLane(prevLaneIndex, laneType, vehicleType, out nextLaneIndex2, out nextlaneId2)) { + NetInfo.Lane lane5 = prevSegmentInfo.m_lanes[nextLaneIndex2]; + byte connectOffset2; + if ((prevSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None == ((byte)(lane5.m_finalDirection & NetInfo.Direction.Backward) != 0)) { + connectOffset2 = 1; + } else { + connectOffset2 = 254; + } + + CustomPathFind.BufferItem item2 = item; + if (this.m_randomParking) { + item2.m_comparisonValue += (float)this.m_pathRandomizer.Int32(300u) / this.m_maxLength; + } +#if DEBUGNEWPF + if (debugPed) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring parking switch\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + + "\t" + $"nextLaneIndex2={nextLaneIndex2}\n" + + "\t" + $"nextlaneId2={nextlaneId2}\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + this.ProcessItemPedBicycle(debugPed, item2, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, connectOffset2, 128, nextLaneIndex2, nextlaneId2); // ped + } + } + } + } else { + // we are going to a non-pedestrian lane + + bool allowPedestrian = (byte)(this.m_laneTypes & NetInfo.LaneType.Pedestrian) != 0; // allow pedestrian switching to vehicle? + bool nextIsBeautificationNode = nextNode.Info.m_class.m_service == ItemClass.Service.Beautification; + bool allowBicycle = false; // is true if cim may switch from a pedestrian lane to a bike lane + byte parkingConnectOffset = 0; + if (allowPedestrian) { + if (prevIsBicycleLane) { + // we are going to a bicycle lane + parkingConnectOffset = connectOffset; + allowBicycle = nextIsBeautificationNode; + } else if (this.m_vehicleLane != 0u) { + // there is a parked vehicle position + if (this.m_vehicleLane != item.m_laneID) { + // we have not reached the parked vehicle yet + allowPedestrian = false; + } else { + // pedestrian switches to parked vehicle + parkingConnectOffset = this.m_vehicleOffset; + } + } else if (this.m_stablePath) { + // enter a bus + parkingConnectOffset = 128; + } else { + // pocket car spawning + if (Options.parkingAI && + queueItem.vehicleType == ExtVehicleType.PassengerCar && + (queueItem.pathType == ExtPathType.WalkingOnly || (queueItem.pathType == ExtPathType.DrivingOnly && item.m_position.m_segment != m_startSegmentA && item.m_position.m_segment != m_startSegmentB))) { + allowPedestrian = false; + } else { + parkingConnectOffset = (byte)this.m_pathRandomizer.UInt32(1u, 254u); + } + } + } + + if ((this.m_vehicleTypes & (VehicleInfo.VehicleType.Ferry /* | VehicleInfo.VehicleType.Monorail*/)) != VehicleInfo.VehicleType.None) { + // monorail / ferry + + for (int k = 0; k < 8; k++) { + ushort nextSegmentId = nextNode.GetSegment(k); + if (nextSegmentId == 0 || nextSegmentId == prevSegmentId) { + continue; + } + + this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle, isMiddle); + } + + if ((nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None /*&& (this._vehicleTypes & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None*/) { - this.ProcessItemCosts(debug, item, nextNodeId, prevSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref prevSegment, ref prevRelSimilarLaneIndex, connectOffset, true, false, isMiddle); - } - } else { - // road vehicles, trams, trains, metros, monorails, etc. - - - // specifies if vehicles should follow lane arrows - bool isStrictLaneChangePolicyEnabled = false; - // specifies if the entity is allowed to u-turn (in general) - bool isEntityAllowedToUturn = (this.m_vehicleTypes & (VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None; - // specifies if thes next node allows for u-turns - bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; - /* - * specifies if u-turns are handled by custom code. - * If not (performCustomVehicleUturns == false) AND the vanilla u-turn condition (stockUturn) evaluates to true, then u-turns are handled by the vanilla code - */ - //bool performCustomVehicleUturns = false; - bool prevIsRouted = prevLaneEndRouting.routed + this.ProcessItemCosts(debug, item, nextNodeId, prevSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref prevSegment, ref prevRelSimilarLaneIndex, connectOffset, true, false, isMiddle); + } + } else { + // road vehicles, trams, trains, metros, monorails, etc. + + + // specifies if vehicles should follow lane arrows + bool isStrictLaneChangePolicyEnabled = false; + // specifies if the entity is allowed to u-turn (in general) + bool isEntityAllowedToUturn = (this.m_vehicleTypes & (VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None; + // specifies if thes next node allows for u-turns + bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; + /* + * specifies if u-turns are handled by custom code. + * If not (performCustomVehicleUturns == false) AND the vanilla u-turn condition (stockUturn) evaluates to true, then u-turns are handled by the vanilla code + */ + //bool performCustomVehicleUturns = false; + bool prevIsRouted = prevLaneEndRouting.routed #if DEBUG - && !m_conf.Debug.Switches[11] -#endif - ; - - if (prevIsRouted) { - bool prevIsOutgoingOneWay = nextIsStartNode ? prevSegmentRouting.startNodeOutgoingOneWay : prevSegmentRouting.endNodeOutgoingOneWay; - bool nextIsUntouchable = (nextNode.m_flags & (NetNode.Flags.Untouchable)) != NetNode.Flags.None; - bool nextIsTransitionOrJunction = (nextNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None; - bool nextIsBend = (nextNode.m_flags & (NetNode.Flags.Bend)) != NetNode.Flags.None; - - // determine if the vehicle may u-turn at the target node according to customization - isUturnAllowedHere = - isUturnAllowedHere || // stock u-turn points - (Options.junctionRestrictionsEnabled && - m_isRoadVehicle && // only road vehicles may perform u-turns - junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode) && // only do u-turns if allowed - !nextIsBeautificationNode && // no u-turns at beautification nodes // TODO refactor to JunctionManager - prevIsCarLane && // u-turns for road vehicles only - !m_isHeavyVehicle && // only small vehicles may perform u-turns - (nextIsTransitionOrJunction || nextIsBend) && // perform u-turns at transitions, junctions and bend nodes // TODO refactor to JunctionManager - !prevIsOutgoingOneWay); // do not u-turn on one-ways // TODO refactor to JunctionManager - - isStrictLaneChangePolicyEnabled = - !nextIsBeautificationNode && // do not obey lane arrows at beautification nodes - !nextIsUntouchable && - m_isLaneArrowObeyingEntity && - //nextIsTransitionOrJunction && // follow lane arrows only at transitions and junctions - !( + && !m_conf.Debug.Switches[11] +#endif + ; + + if (prevIsRouted) { + bool prevIsOutgoingOneWay = nextIsStartNode ? prevSegmentRouting.startNodeOutgoingOneWay : prevSegmentRouting.endNodeOutgoingOneWay; + bool nextIsUntouchable = (nextNode.m_flags & (NetNode.Flags.Untouchable)) != NetNode.Flags.None; + bool nextIsTransitionOrJunction = (nextNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None; + bool nextIsBend = (nextNode.m_flags & (NetNode.Flags.Bend)) != NetNode.Flags.None; + + // determine if the vehicle may u-turn at the target node according to customization + isUturnAllowedHere = + isUturnAllowedHere || // stock u-turn points + (Options.junctionRestrictionsEnabled && + m_isRoadVehicle && // only road vehicles may perform u-turns + junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode) && // only do u-turns if allowed + !nextIsBeautificationNode && // no u-turns at beautification nodes // TODO refactor to JunctionManager + prevIsCarLane && // u-turns for road vehicles only + !m_isHeavyVehicle && // only small vehicles may perform u-turns + (nextIsTransitionOrJunction || nextIsBend) && // perform u-turns at transitions, junctions and bend nodes // TODO refactor to JunctionManager + !prevIsOutgoingOneWay); // do not u-turn on one-ways // TODO refactor to JunctionManager + + isStrictLaneChangePolicyEnabled = + !nextIsBeautificationNode && // do not obey lane arrows at beautification nodes + !nextIsUntouchable && + m_isLaneArrowObeyingEntity && + //nextIsTransitionOrJunction && // follow lane arrows only at transitions and junctions + !( #if DEBUG - Options.allRelaxed || // debug option: all vehicle may ignore lane arrows -#endif - (Options.relaxedBusses && queueItem.vehicleType == ExtVehicleType.Bus)); // option: busses may ignore lane arrows - - /*if (! performCustomVehicleUturns) { - isUturnAllowedHere = false; - }*/ - //isEntityAllowedToUturn = isEntityAllowedToUturn && !performCustomVehicleUturns; - - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane} (id {item.m_laneID}), node {nextNodeId} ({nextIsStartNode}):\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_vehicleLane={m_vehicleLane}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"prevIsOutgoingOneWay={prevIsOutgoingOneWay}\n" + - "\t" + $"prevIsRouted={prevIsRouted}\n\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" + - "\t" + $"isNextBeautificationNode={nextIsBeautificationNode}\n" + - //"\t" + $"nextIsRealJunction={nextIsRealJunction}\n" + - "\t" + $"nextIsTransitionOrJunction={nextIsTransitionOrJunction}\n" + - "\t" + $"nextIsBend={nextIsBend}\n" + - "\t" + $"nextIsUntouchable={nextIsUntouchable}\n" + - "\t" + $"allowBicycle={allowBicycle}\n" + - "\t" + $"isCustomUturnAllowed={junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)}\n" + - "\t" + $"isStrictLaneArrowPolicyEnabled={isStrictLaneChangePolicyEnabled}\n" + - "\t" + $"isEntityAllowedToUturn={isEntityAllowedToUturn}\n" + - "\t" + $"isUturnAllowedHere={isUturnAllowedHere}\n" - //"\t" + $"performCustomVehicleUturns={performCustomVehicleUturns}\n" - ); -#endif - } else { -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}):\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"prevIsRouted={prevIsRouted}\n\n" - ); -#endif - } - - if (allowBicycle || !prevIsRouted) { - /* - * pedestrian to bicycle lane switch or no routing information available: - * if pedestrian lanes should be explored (allowBicycle == true): do this here - * if previous segment has custom routing (prevIsRouted == true): do NOT explore vehicle lanes here, else: vanilla exploration of vehicle lanes - */ - -#if DEBUGNEWPF - if (debug) { - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"-> using DEFAULT exploration mode\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - - /*if (performCustomVehicleUturns) { - isUturnAllowedHere = true; - isEntityAllowedToUturn = true; - }*/ - - ushort nextSegmentId = prevSegment.GetRightSegment(nextNodeId); - for (int k = 0; k < 8; ++k) { - if (nextSegmentId == 0 || nextSegmentId == prevSegmentId) { - break; - } - - if (ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsRouted, allowBicycle, isMiddle)) { - // exceptional u-turns - isUturnAllowedHere = true; - } - - nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); - } - } - - if (prevIsRouted) { - /* routed vehicle paths */ - -#if DEBUGNEWPF - if (debug) - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"-> using CUSTOM exploration mode\n" - ); -#endif - bool canUseLane = CanUseLane(debug, item.m_position.m_segment, prevSegmentInfo, item.m_position.m_lane, prevLaneInfo); - LaneTransitionData[] laneTransitions = prevLaneEndRouting.transitions; - if (laneTransitions != null && (canUseLane || Options.vehicleRestrictionsAggression != VehicleRestrictionsAggression.Strict)) { - -#if DEBUGNEWPF - if (debug) - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"CUSTOM exploration\n" - ); -#endif - - LaneChangingCostCalculationMode laneChangingCostCalculationMode = LaneChangingCostCalculationMode.None; // lane changing cost calculation mode to use - float? segmentSelectionCost = null; // cost for using that particular segment - float? laneSelectionCost = null; // cost for using that particular lane - - /* - * ======================================================================================================= - * (1) Apply vehicle restrictions - * ======================================================================================================= - */ - - if (! canUseLane) { - laneSelectionCost = VehicleRestrictionsManager.PATHFIND_PENALTIES[(int)Options.vehicleRestrictionsAggression]; - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied vehicle restrictions for vehicle {queueItem.vehicleId}, type {queueItem.vehicleType}:\n" + - "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" - ); -#endif - } - - if (m_isRoadVehicle && - prevLaneInfo != null && - prevIsCarLane) { - - if (Options.advancedAI) { - laneChangingCostCalculationMode = LaneChangingCostCalculationMode.ByGivenDistance; -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"AI is active, prev is car lane and we are a car\n" - ); -#endif - } - - /* - * ======================================================================================================= - * (2) Apply car ban district policies - * ======================================================================================================= - */ - - // Apply costs for traffic ban policies - if ((prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && - (prevLaneInfo.m_vehicleType & this.m_vehicleTypes) == VehicleInfo.VehicleType.Car && - (netManager.m_segments.m_buffer[item.m_position.m_segment].m_flags & this.m_carBanMask) != NetSegment.Flags.None) { - // heavy vehicle ban / car ban ("Old Town" policy) - if (laneSelectionCost == null) { - laneSelectionCost = 1f; - } -#if DEBUGNEWPF - float? oldLaneSelectionCost = laneSelectionCost; -#endif - laneSelectionCost *= 7.5f; - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied heavy vehicle ban / car ban ('Old Town' policy):\n" + - "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + - "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" - ); -#endif - } - - /* - * ======================================================================================================= - * (3) Apply costs for using/not using transport lanes - * ======================================================================================================= - */ - - /* - * (1) busses should prefer transport lanes - * (2) regular traffic should prefer regular lanes - * (3) taxis, service vehicles and emergency vehicles may choose freely between regular and transport lanes - */ - if ((prevLaneInfo.m_laneType & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None) { - // previous lane is a public transport lane - if ((queueItem.vehicleType & ExtVehicleType.Bus) != ExtVehicleType.None) { - if (laneSelectionCost == null) { - laneSelectionCost = 1f; - } -#if DEBUGNEWPF - float? oldLaneSelectionCost = laneSelectionCost; -#endif - laneSelectionCost *= m_conf.PathFinding.PublicTransportLaneReward; // (1) -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied bus-on-transport lane reward:\n" + - "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + - "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" - ); -#endif - } else if ((queueItem.vehicleType & (ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service | ExtVehicleType.Emergency)) == ExtVehicleType.None) { - if (laneSelectionCost == null) { - laneSelectionCost = 1f; - } -#if DEBUGNEWPF - float? oldLaneSelectionCost = laneSelectionCost; -#endif - laneSelectionCost *= m_conf.PathFinding.PublicTransportLanePenalty; // (2) -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied car-on-transport lane penalty:\n" + - "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + - "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" - ); -#endif - } else { - // (3), do nothing - } - } - - /* - * ======================================================================================================= - * (4) Apply costs for large vehicles using inner lanes on highways - * ======================================================================================================= - */ - - bool nextIsJunction = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; - bool nextIsRealJunction = nextIsJunction && (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); - ushort prevNodeId = (nextNodeId == prevSegment.m_startNode) ? prevSegment.m_endNode : prevSegment.m_startNode; - //bool prevIsRealJunction = (netManager.m_nodes.m_buffer[prevNodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); - if (prevLaneInfo.m_similarLaneCount > 1) { - if (m_isHeavyVehicle && - Options.preferOuterLane && - prevSegmentRouting.highway && - m_pathRandomizer.Int32(m_conf.PathFinding.HeavyVehicleInnerLanePenaltySegmentSel) == 0 - /* && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None */ - ) { - // penalize large vehicles for using inner lanes - if (laneSelectionCost == null) { - laneSelectionCost = 1f; - } -#if DEBUGNEWPF - float? oldLaneSelectionCost = laneSelectionCost; -#endif - float prevRelOuterLane = ((float)prevOuterSimilarLaneIndex / (float)(prevLaneInfo.m_similarLaneCount - 1)); - laneSelectionCost *= 1f + m_conf.PathFinding.HeavyVehicleMaxInnerLanePenalty * prevRelOuterLane; -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied inner lane penalty:\n" + - "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + - "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" - ); -#endif - } - - /* - * ======================================================================================================= - * (5) Apply costs for randomized lane selection in front of junctions and highway transitions - * ======================================================================================================= - */ - if (Options.advancedAI && - !m_stablePath && - !m_isHeavyVehicle && - nextIsJunction && - m_pathRandomizer.Int32(m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel) == 0) { - // randomized lane selection at junctions - if (laneSelectionCost == null) { - laneSelectionCost = 1f; - } -#if DEBUGNEWPF - float? oldLaneSelectionCost = laneSelectionCost; -#endif - laneSelectionCost *= 1f + m_pathRandomizer.Int32(2) * m_conf.AdvancedVehicleAI.LaneRandomizationCostFactor; -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied lane randomizations at junctions:\n" + - "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + - "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" - ); -#endif - } - } - - /* - * ======================================================================================================= - * (6) Apply junction costs - * ======================================================================================================= - */ - if (Options.advancedAI && nextIsJunction && prevSegmentRouting.highway) { - if (segmentSelectionCost == null) { - segmentSelectionCost = 1f; - } - - segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.JunctionBaseCost; - } - - /* - * ======================================================================================================= - * (7) Apply traffic measurement costs for segment selection - * ======================================================================================================= - */ - if (Options.advancedAI && (queueItem.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) != ExtVehicleType.None && !m_stablePath) { - // segment selection based on segment traffic volume - NetInfo.Direction prevFinalDir = nextIsStartNode ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; - prevFinalDir = ((prevSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? prevFinalDir : NetInfo.InvertDirection(prevFinalDir); - SegmentDirTrafficData prevDirTrafficData = trafficMeasurementManager.SegmentDirTrafficData[trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)]; - - float segmentTraffic = Mathf.Clamp(1f - (float)prevDirTrafficData.meanSpeed / (float)TrafficMeasurementManager.REF_REL_SPEED + item.m_trafficRand, 0, 1f); - - if (segmentSelectionCost == null) { - segmentSelectionCost = 1f; - } - - segmentSelectionCost *= 1f + - m_conf.AdvancedVehicleAI.TrafficCostFactor * - segmentTraffic; - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied traffic measurement costs for segment selection:\n" + - "\t" + $"segmentTraffic={segmentTraffic}\n" + - "\t" + $"=> segmentSelectionCost={segmentSelectionCost}\n" - ); -#endif - - if (m_conf.AdvancedVehicleAI.LaneDensityRandInterval > 0 && nextIsRealJunction) { - item.m_trafficRand = 0.01f * ((float)m_pathRandomizer.Int32((uint)m_conf.AdvancedVehicleAI.LaneDensityRandInterval + 1u) - m_conf.AdvancedVehicleAI.LaneDensityRandInterval / 2f); - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"updated item.m_trafficRand:\n" + - "\t" + $"=> item.m_trafficRand={item.m_trafficRand}\n" - ); -#endif - } - } - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"calculated traffic stats:\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"laneSelectionCost={laneSelectionCost}\n" + - "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - ); -#endif - } - - for (int k = 0; k < laneTransitions.Length; ++k) { - ushort nextSegmentId = laneTransitions[k].segmentId; - - if (nextSegmentId == 0) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"CUSTOM exploration\n" + - "\t" + $"transition iteration {k}:\n" + - "\t" + $"{laneTransitions[k].ToString()}\n" + - "\t" + $"*** SKIPPING *** (nextSegmentId=0)\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - continue; - } - - bool uturn = nextSegmentId == prevSegmentId; - if (uturn) { - // prevent double/forbidden exploration of previous segment by vanilla code during this method execution - if (! isEntityAllowedToUturn || ! isUturnAllowedHere) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"CUSTOM exploration\n" + - "\t" + $"transition iteration {k}:\n" + - "\t" + $"{laneTransitions[k].ToString()}\n" + - "\t" + $"*** SKIPPING *** (u-turns prohibited)\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - continue; - } - } - - if (laneTransitions[k].type == LaneEndTransitionType.Invalid) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"CUSTOM exploration\n" + - "\t" + $"transition iteration {k}:\n" + - "\t" + $"{laneTransitions[k].ToString()}\n" + - "\t" + $"*** SKIPPING *** (invalid transition)\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - continue; - } - - // allow vehicles to ignore strict lane routing when moving off - bool relaxedLaneChanging = - m_isRoadVehicle && - (queueItem.vehicleType & (ExtVehicleType.Service | ExtVehicleType.PublicTransport | ExtVehicleType.Emergency)) != ExtVehicleType.None && - queueItem.vehicleId == 0 && - (laneTransitions[k].laneId == m_startLaneA || laneTransitions[k].laneId == m_startLaneB); - - if (! relaxedLaneChanging && - (isStrictLaneChangePolicyEnabled && laneTransitions[k].type == LaneEndTransitionType.Relaxed)) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"CUSTOM exploration\n" + - "\t" + $"transition iteration {k}:\n" + - "\t" + $"{laneTransitions[k].ToString()}\n" + - "\t" + $"relaxedLaneChanging={relaxedLaneChanging}\n" + - "\t" + $"isStrictLaneChangePolicyEnabled={relaxedLaneChanging}\n" + - "\t" + $"*** SKIPPING *** (incompatible lane)\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - continue; - } - -#if DEBUGNEWPF - if (debug) { - logBuf.Add( - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"CUSTOM exploration\n" + - "\t" + $"transition iteration {k}:\n" + - "\t" + $"{laneTransitions[k].ToString()}\n" + - "\t" + $"> PERFORMING EXPLORATION NOW <\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - - bool foundForced = false; - int prevLaneIndexFromInner = prevRelSimilarLaneIndex; - if (ProcessItemCosts(debug, false, laneChangingCostCalculationMode, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], /*routingManager.segmentRoutings[nextSegmentId],*/ ref prevLaneIndexFromInner, connectOffset, true, false, laneTransitions[k].laneIndex, laneTransitions[k].laneId, laneTransitions[k].distance, segmentSelectionCost, laneSelectionCost, isMiddle, out foundForced)) { - // process exceptional u-turning in vanilla code - isUturnAllowedHere = true; - } - } - } - } - - if (!prevIsRouted && isEntityAllowedToUturn && isUturnAllowedHere) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"path unit {unitId}\n" + - $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"-> exploring DEFAULT u-turn\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - - this.ProcessItemCosts(debug, item, nextNodeId, prevSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref prevSegment, ref prevRelSimilarLaneIndex, connectOffset, true, false, isMiddle); - } - } - - if (allowPedestrian) { - // switch from walking to driving a car, bus, etc. - int nextLaneIndex; - uint nextLaneId; - if (prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Pedestrian, this.m_vehicleTypes, out nextLaneIndex, out nextLaneId)) { -#if DEBUGNEWPF - if (debugPed) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring vehicle switch\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" + - "\t" + $"nextLaneIndex={nextLaneIndex}\n" + - "\t" + $"nextLaneId={nextLaneId}\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - this.ProcessItemPedBicycle(debugPed, item, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, parkingConnectOffset, parkingConnectOffset, nextLaneIndex, nextLaneId); // ped - } - } // allowPedSwitch - } // !prevIsPedestrianLane - - // [18/05/06] conditions commented out because cims could not go to an outside connection with path "walk -> public transport -> walk -> car" - if (nextNode.m_lane != 0u /*&& + Options.allRelaxed || // debug option: all vehicle may ignore lane arrows +#endif + (Options.relaxedBusses && queueItem.vehicleType == ExtVehicleType.Bus)); // option: busses may ignore lane arrows + + /*if (! performCustomVehicleUturns) { + isUturnAllowedHere = false; + }*/ + //isEntityAllowedToUturn = isEntityAllowedToUturn && !performCustomVehicleUturns; + + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane} (id {item.m_laneID}), node {nextNodeId} ({nextIsStartNode}):\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_vehicleLane={m_vehicleLane}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"prevIsOutgoingOneWay={prevIsOutgoingOneWay}\n" + + "\t" + $"prevIsRouted={prevIsRouted}\n\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + + "\t" + $"isNextBeautificationNode={nextIsBeautificationNode}\n" + + //"\t" + $"nextIsRealJunction={nextIsRealJunction}\n" + + "\t" + $"nextIsTransitionOrJunction={nextIsTransitionOrJunction}\n" + + "\t" + $"nextIsBend={nextIsBend}\n" + + "\t" + $"nextIsUntouchable={nextIsUntouchable}\n" + + "\t" + $"allowBicycle={allowBicycle}\n" + + "\t" + $"isCustomUturnAllowed={junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)}\n" + + "\t" + $"isStrictLaneArrowPolicyEnabled={isStrictLaneChangePolicyEnabled}\n" + + "\t" + $"isEntityAllowedToUturn={isEntityAllowedToUturn}\n" + + "\t" + $"isUturnAllowedHere={isUturnAllowedHere}\n" + //"\t" + $"performCustomVehicleUturns={performCustomVehicleUturns}\n" + ); +#endif + } else { +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}):\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"prevIsRouted={prevIsRouted}\n\n" + ); +#endif + } + + if (allowBicycle || !prevIsRouted) { + /* + * pedestrian to bicycle lane switch or no routing information available: + * if pedestrian lanes should be explored (allowBicycle == true): do this here + * if previous segment has custom routing (prevIsRouted == true): do NOT explore vehicle lanes here, else: vanilla exploration of vehicle lanes + */ + +#if DEBUGNEWPF + if (debug) { + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"-> using DEFAULT exploration mode\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + + /*if (performCustomVehicleUturns) { + isUturnAllowedHere = true; + isEntityAllowedToUturn = true; + }*/ + + ushort nextSegmentId = prevSegment.GetRightSegment(nextNodeId); + for (int k = 0; k < 8; ++k) { + if (nextSegmentId == 0 || nextSegmentId == prevSegmentId) { + break; + } + + if (ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsRouted, allowBicycle, isMiddle)) { + // exceptional u-turns + isUturnAllowedHere = true; + } + + nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); + } + } + + if (prevIsRouted) { + /* routed vehicle paths */ + +#if DEBUGNEWPF + if (debug) + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"-> using CUSTOM exploration mode\n" + ); +#endif + bool canUseLane = CanUseLane(debug, item.m_position.m_segment, prevSegmentInfo, item.m_position.m_lane, prevLaneInfo); + LaneTransitionData[] laneTransitions = prevLaneEndRouting.transitions; + if (laneTransitions != null && (canUseLane || Options.vehicleRestrictionsAggression != VehicleRestrictionsAggression.Strict)) { + +#if DEBUGNEWPF + if (debug) + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"CUSTOM exploration\n" + ); +#endif + + LaneChangingCostCalculationMode laneChangingCostCalculationMode = LaneChangingCostCalculationMode.None; // lane changing cost calculation mode to use + float? segmentSelectionCost = null; // cost for using that particular segment + float? laneSelectionCost = null; // cost for using that particular lane + + /* + * ======================================================================================================= + * (1) Apply vehicle restrictions + * ======================================================================================================= + */ + + if (! canUseLane) { + laneSelectionCost = VehicleRestrictionsManager.PATHFIND_PENALTIES[(int)Options.vehicleRestrictionsAggression]; + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied vehicle restrictions for vehicle {queueItem.vehicleId}, type {queueItem.vehicleType}:\n" + + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" + ); +#endif + } + + if (m_isRoadVehicle && + prevLaneInfo != null && + prevIsCarLane) { + + if (Options.advancedAI) { + laneChangingCostCalculationMode = LaneChangingCostCalculationMode.ByGivenDistance; +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"AI is active, prev is car lane and we are a car\n" + ); +#endif + } + + /* + * ======================================================================================================= + * (2) Apply car ban district policies + * ======================================================================================================= + */ + + // Apply costs for traffic ban policies + if ((prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && + (prevLaneInfo.m_vehicleType & this.m_vehicleTypes) == VehicleInfo.VehicleType.Car && + (netManager.m_segments.m_buffer[item.m_position.m_segment].m_flags & this.m_carBanMask) != NetSegment.Flags.None) { + // heavy vehicle ban / car ban ("Old Town" policy) + if (laneSelectionCost == null) { + laneSelectionCost = 1f; + } +#if DEBUGNEWPF + float? oldLaneSelectionCost = laneSelectionCost; +#endif + laneSelectionCost *= 7.5f; + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied heavy vehicle ban / car ban ('Old Town' policy):\n" + + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" + ); +#endif + } + + /* + * ======================================================================================================= + * (3) Apply costs for using/not using transport lanes + * ======================================================================================================= + */ + + /* + * (1) busses should prefer transport lanes + * (2) regular traffic should prefer regular lanes + * (3) taxis, service vehicles and emergency vehicles may choose freely between regular and transport lanes + */ + if ((prevLaneInfo.m_laneType & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None) { + // previous lane is a public transport lane + if ((queueItem.vehicleType & ExtVehicleType.Bus) != ExtVehicleType.None) { + if (laneSelectionCost == null) { + laneSelectionCost = 1f; + } +#if DEBUGNEWPF + float? oldLaneSelectionCost = laneSelectionCost; +#endif + laneSelectionCost *= m_conf.PathFinding.PublicTransportLaneReward; // (1) +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied bus-on-transport lane reward:\n" + + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" + ); +#endif + } else if ((queueItem.vehicleType & (ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service | ExtVehicleType.Emergency)) == ExtVehicleType.None) { + if (laneSelectionCost == null) { + laneSelectionCost = 1f; + } +#if DEBUGNEWPF + float? oldLaneSelectionCost = laneSelectionCost; +#endif + laneSelectionCost *= m_conf.PathFinding.PublicTransportLanePenalty; // (2) +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied car-on-transport lane penalty:\n" + + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" + ); +#endif + } else { + // (3), do nothing + } + } + + /* + * ======================================================================================================= + * (4) Apply costs for large vehicles using inner lanes on highways + * ======================================================================================================= + */ + + bool nextIsJunction = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; + bool nextIsRealJunction = nextIsJunction && (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); + ushort prevNodeId = (nextNodeId == prevSegment.m_startNode) ? prevSegment.m_endNode : prevSegment.m_startNode; + //bool prevIsRealJunction = (netManager.m_nodes.m_buffer[prevNodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); + if (prevLaneInfo.m_similarLaneCount > 1) { + if (m_isHeavyVehicle && + Options.preferOuterLane && + prevSegmentRouting.highway && + m_pathRandomizer.Int32(m_conf.PathFinding.HeavyVehicleInnerLanePenaltySegmentSel) == 0 + /* && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None */ + ) { + // penalize large vehicles for using inner lanes + if (laneSelectionCost == null) { + laneSelectionCost = 1f; + } +#if DEBUGNEWPF + float? oldLaneSelectionCost = laneSelectionCost; +#endif + float prevRelOuterLane = ((float)prevOuterSimilarLaneIndex / (float)(prevLaneInfo.m_similarLaneCount - 1)); + laneSelectionCost *= 1f + m_conf.PathFinding.HeavyVehicleMaxInnerLanePenalty * prevRelOuterLane; +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied inner lane penalty:\n" + + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" + ); +#endif + } + + /* + * ======================================================================================================= + * (5) Apply costs for randomized lane selection in front of junctions and highway transitions + * ======================================================================================================= + */ + if (Options.advancedAI && + !m_stablePath && + !m_isHeavyVehicle && + nextIsJunction && + m_pathRandomizer.Int32(m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel) == 0) { + // randomized lane selection at junctions + if (laneSelectionCost == null) { + laneSelectionCost = 1f; + } +#if DEBUGNEWPF + float? oldLaneSelectionCost = laneSelectionCost; +#endif + laneSelectionCost *= 1f + m_pathRandomizer.Int32(2) * m_conf.AdvancedVehicleAI.LaneRandomizationCostFactor; +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied lane randomizations at junctions:\n" + + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" + ); +#endif + } + } + + /* + * ======================================================================================================= + * (6) Apply junction costs + * ======================================================================================================= + */ + if (Options.advancedAI && nextIsJunction && prevSegmentRouting.highway) { + if (segmentSelectionCost == null) { + segmentSelectionCost = 1f; + } + + segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.JunctionBaseCost; + } + + /* + * ======================================================================================================= + * (7) Apply traffic measurement costs for segment selection + * ======================================================================================================= + */ + if (Options.advancedAI && (queueItem.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) != ExtVehicleType.None && !m_stablePath) { + // segment selection based on segment traffic volume + NetInfo.Direction prevFinalDir = nextIsStartNode ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; + prevFinalDir = ((prevSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? prevFinalDir : NetInfo.InvertDirection(prevFinalDir); + SegmentDirTrafficData prevDirTrafficData = trafficMeasurementManager.SegmentDirTrafficData[trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)]; + + float segmentTraffic = Mathf.Clamp(1f - (float)prevDirTrafficData.meanSpeed / (float)TrafficMeasurementManager.REF_REL_SPEED + item.m_trafficRand, 0, 1f); + + if (segmentSelectionCost == null) { + segmentSelectionCost = 1f; + } + + segmentSelectionCost *= 1f + + m_conf.AdvancedVehicleAI.TrafficCostFactor * + segmentTraffic; + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied traffic measurement costs for segment selection:\n" + + "\t" + $"segmentTraffic={segmentTraffic}\n" + + "\t" + $"=> segmentSelectionCost={segmentSelectionCost}\n" + ); +#endif + + if (m_conf.AdvancedVehicleAI.LaneDensityRandInterval > 0 && nextIsRealJunction) { + item.m_trafficRand = 0.01f * ((float)m_pathRandomizer.Int32((uint)m_conf.AdvancedVehicleAI.LaneDensityRandInterval + 1u) - m_conf.AdvancedVehicleAI.LaneDensityRandInterval / 2f); + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"updated item.m_trafficRand:\n" + + "\t" + $"=> item.m_trafficRand={item.m_trafficRand}\n" + ); +#endif + } + } + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"calculated traffic stats:\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + ); +#endif + } + + for (int k = 0; k < laneTransitions.Length; ++k) { + ushort nextSegmentId = laneTransitions[k].segmentId; + + if (nextSegmentId == 0) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"CUSTOM exploration\n" + + "\t" + $"transition iteration {k}:\n" + + "\t" + $"{laneTransitions[k].ToString()}\n" + + "\t" + $"*** SKIPPING *** (nextSegmentId=0)\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + continue; + } + + bool uturn = nextSegmentId == prevSegmentId; + if (uturn) { + // prevent double/forbidden exploration of previous segment by vanilla code during this method execution + if (! isEntityAllowedToUturn || ! isUturnAllowedHere) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"CUSTOM exploration\n" + + "\t" + $"transition iteration {k}:\n" + + "\t" + $"{laneTransitions[k].ToString()}\n" + + "\t" + $"*** SKIPPING *** (u-turns prohibited)\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + continue; + } + } + + if (laneTransitions[k].type == LaneEndTransitionType.Invalid) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"CUSTOM exploration\n" + + "\t" + $"transition iteration {k}:\n" + + "\t" + $"{laneTransitions[k].ToString()}\n" + + "\t" + $"*** SKIPPING *** (invalid transition)\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + continue; + } + + // allow vehicles to ignore strict lane routing when moving off + bool relaxedLaneChanging = + m_isRoadVehicle && + (queueItem.vehicleType & (ExtVehicleType.Service | ExtVehicleType.PublicTransport | ExtVehicleType.Emergency)) != ExtVehicleType.None && + queueItem.vehicleId == 0 && + (laneTransitions[k].laneId == m_startLaneA || laneTransitions[k].laneId == m_startLaneB); + + if (! relaxedLaneChanging && + (isStrictLaneChangePolicyEnabled && laneTransitions[k].type == LaneEndTransitionType.Relaxed)) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"CUSTOM exploration\n" + + "\t" + $"transition iteration {k}:\n" + + "\t" + $"{laneTransitions[k].ToString()}\n" + + "\t" + $"relaxedLaneChanging={relaxedLaneChanging}\n" + + "\t" + $"isStrictLaneChangePolicyEnabled={relaxedLaneChanging}\n" + + "\t" + $"*** SKIPPING *** (incompatible lane)\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + continue; + } + +#if DEBUGNEWPF + if (debug) { + logBuf.Add( + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"CUSTOM exploration\n" + + "\t" + $"transition iteration {k}:\n" + + "\t" + $"{laneTransitions[k].ToString()}\n" + + "\t" + $"> PERFORMING EXPLORATION NOW <\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + + bool foundForced = false; + int prevLaneIndexFromInner = prevRelSimilarLaneIndex; + if (ProcessItemCosts(debug, false, laneChangingCostCalculationMode, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], /*routingManager.segmentRoutings[nextSegmentId],*/ ref prevLaneIndexFromInner, connectOffset, true, false, laneTransitions[k].laneIndex, laneTransitions[k].laneId, laneTransitions[k].distance, segmentSelectionCost, laneSelectionCost, isMiddle, out foundForced)) { + // process exceptional u-turning in vanilla code + isUturnAllowedHere = true; + } + } + } + } + + if (!prevIsRouted && isEntityAllowedToUturn && isUturnAllowedHere) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"path unit {unitId}\n" + + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"-> exploring DEFAULT u-turn\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + + this.ProcessItemCosts(debug, item, nextNodeId, prevSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref prevSegment, ref prevRelSimilarLaneIndex, connectOffset, true, false, isMiddle); + } + } + + if (allowPedestrian) { + // switch from walking to driving a car, bus, etc. + int nextLaneIndex; + uint nextLaneId; + if (prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Pedestrian, this.m_vehicleTypes, out nextLaneIndex, out nextLaneId)) { +#if DEBUGNEWPF + if (debugPed) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring vehicle switch\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + + "\t" + $"nextLaneId={nextLaneId}\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + this.ProcessItemPedBicycle(debugPed, item, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, parkingConnectOffset, parkingConnectOffset, nextLaneIndex, nextLaneId); // ped + } + } // allowPedSwitch + } // !prevIsPedestrianLane + + // [18/05/06] conditions commented out because cims could not go to an outside connection with path "walk -> public transport -> walk -> car" + if (nextNode.m_lane != 0u /*&& (!Options.parkingAI || queueItem.vehicleType != ExtVehicleType.PassengerCar || (item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) == NetInfo.LaneType.None)*/) { - // transport lines, cargo lines, etc. - - bool targetDisabled = (nextNode.m_flags & (NetNode.Flags.Disabled | NetNode.Flags.DisableOnlyMiddle)) == NetNode.Flags.Disabled; - ushort nextSegmentId = netManager.m_lanes.m_buffer[nextNode.m_lane].m_segment; - if (nextSegmentId != 0 && nextSegmentId != item.m_position.m_segment) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring transport segment\n" + - "\t" + $"_extPathType={queueItem.pathType}\n" + - "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"_stablePath={m_stablePath}\n" + - "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + - "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + - "\t" + $"nextNode.m_lane={nextNode.m_lane}\n" + - "\t" + $"nextSegmentId={nextSegmentId}\n" + - "\t" + $"nextIsStartNode={nextIsStartNode}\n" - ); - FlushMainLog(logBuf, unitId); - } -#endif - this.ProcessItemPublicTransport(debug, item, nextNodeId, targetDisabled, nextSegmentId, ref prevSegment, ref netManager.m_segments.m_buffer[nextSegmentId], nextNode.m_lane, nextNode.m_laneOffset, connectOffset); - } - } - -#if DEBUGNEWPF - if (debug) { - FlushMainLog(logBuf, unitId); - } -#endif - } - - // 2 - private void ProcessItemPublicTransport(bool debug, BufferItem item, ushort nextNodeId, bool targetDisabled, ushort nextSegmentId, ref NetSegment prevSegment, ref NetSegment nextSegment, uint nextLaneId, byte offset, byte connectOffset) { - if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { - return; - } - NetManager netManager = Singleton.instance; - if (targetDisabled && ((netManager.m_nodes.m_buffer[(int)nextSegment.m_startNode].m_flags | netManager.m_nodes.m_buffer[(int)nextSegment.m_endNode].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None) { - return; - } + // transport lines, cargo lines, etc. + + bool targetDisabled = (nextNode.m_flags & (NetNode.Flags.Disabled | NetNode.Flags.DisableOnlyMiddle)) == NetNode.Flags.Disabled; + ushort nextSegmentId = netManager.m_lanes.m_buffer[nextNode.m_lane].m_segment; + if (nextSegmentId != 0 && nextSegmentId != item.m_position.m_segment) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring transport segment\n" + + "\t" + $"_extPathType={queueItem.pathType}\n" + + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"_stablePath={m_stablePath}\n" + + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + + "\t" + $"nextNode.m_lane={nextNode.m_lane}\n" + + "\t" + $"nextSegmentId={nextSegmentId}\n" + + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + ); + FlushMainLog(logBuf, unitId); + } +#endif + this.ProcessItemPublicTransport(debug, item, nextNodeId, targetDisabled, nextSegmentId, ref prevSegment, ref netManager.m_segments.m_buffer[nextSegmentId], nextNode.m_lane, nextNode.m_laneOffset, connectOffset); + } + } + +#if DEBUGNEWPF + if (debug) { + FlushMainLog(logBuf, unitId); + } +#endif + } + + // 2 + private void ProcessItemPublicTransport(bool debug, BufferItem item, ushort nextNodeId, bool targetDisabled, ushort nextSegmentId, ref NetSegment prevSegment, ref NetSegment nextSegment, uint nextLaneId, byte offset, byte connectOffset) { + if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { + return; + } + NetManager netManager = Singleton.instance; + if (targetDisabled && ((netManager.m_nodes.m_buffer[(int)nextSegment.m_startNode].m_flags | netManager.m_nodes.m_buffer[(int)nextSegment.m_endNode].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None) { + return; + } #if COUNTSEGMENTSTONEXTJUNCTION bool nextIsRegularNode = nextNodeId == prevSegment.m_startNode || nextNodeId == prevSegment.m_endNode; @@ -1659,60 +1664,60 @@ private void ProcessItemPublicTransport(bool debug, BufferItem item, ushort next } #endif - NetInfo nextSegmentInfo = nextSegment.Info; - NetInfo prevSegmentInfo = prevSegment.Info; - int nextNumLanes = nextSegmentInfo.m_lanes.Length; - uint curLaneId = nextSegment.m_lanes; - float prevMaxSpeed = 1f; - float prevSpeed = 1f; - NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; - if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { - NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; - prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // NON-STOCK CODE - prevLaneType = prevLaneInfo.m_laneType; - if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - prevSpeed = this.CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE - } - float segLength; - if (prevLaneType == NetInfo.LaneType.PublicTransport) { - segLength = netManager.m_lanes.m_buffer[item.m_laneID].m_length; - } else { - segLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); - } - float offsetLength = (float)Mathf.Abs((int)(connectOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * segLength; - float methodDistance = item.m_methodDistance + offsetLength; - float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * this.m_maxLength); - float duration = item.m_duration + offsetLength / prevMaxSpeed; - Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - - if (! this.m_ignoreCost) { - int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; - if (ticketCost != 0) { - comparisonValue += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; - } - } - - uint laneIndex = 0; + NetInfo nextSegmentInfo = nextSegment.Info; + NetInfo prevSegmentInfo = prevSegment.Info; + int nextNumLanes = nextSegmentInfo.m_lanes.Length; + uint curLaneId = nextSegment.m_lanes; + float prevMaxSpeed = 1f; + float prevSpeed = 1f; + NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; + if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { + NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; + prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // NON-STOCK CODE + prevLaneType = prevLaneInfo.m_laneType; + if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + prevSpeed = this.CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE + } + float segLength; + if (prevLaneType == NetInfo.LaneType.PublicTransport) { + segLength = netManager.m_lanes.m_buffer[item.m_laneID].m_length; + } else { + segLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); + } + float offsetLength = (float)Mathf.Abs((int)(connectOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * segLength; + float methodDistance = item.m_methodDistance + offsetLength; + float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * this.m_maxLength); + float duration = item.m_duration + offsetLength / prevMaxSpeed; + Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + + if (! this.m_ignoreCost) { + int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; + if (ticketCost != 0) { + comparisonValue += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; + } + } + + uint laneIndex = 0; #if DEBUG - int wIter = 0; + int wIter = 0; #endif - while (laneIndex < nextNumLanes && curLaneId != 0u) { + while (laneIndex < nextNumLanes && curLaneId != 0u) { #if DEBUG - ++wIter; - if (wIter >= 20) { - Log.Error("Too many iterations in ProcessItem2!"); - break; - } -#endif - - if (nextLaneId == curLaneId) { - NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[laneIndex]; - if (nextLaneInfo.CheckType(this.m_laneTypes, this.m_vehicleTypes)) { - Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)offset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - float distance = Vector3.Distance(a, b); - BufferItem nextItem; + ++wIter; + if (wIter >= 20) { + Log.Error("Too many iterations in ProcessItem2!"); + break; + } +#endif + + if (nextLaneId == curLaneId) { + NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[laneIndex]; + if (nextLaneInfo.CheckType(this.m_laneTypes, this.m_vehicleTypes)) { + Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)offset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + float distance = Vector3.Distance(a, b); + BufferItem nextItem; #if COUNTSEGMENTSTONEXTJUNCTION // NON-STOCK CODE START // if (prevIsRealJunction) { @@ -1722,323 +1727,323 @@ private void ProcessItemPublicTransport(bool debug, BufferItem item, ushort next } // NON-STOCK CODE END // #endif - nextItem.m_position.m_segment = nextSegmentId; - nextItem.m_position.m_lane = (byte)laneIndex; - nextItem.m_position.m_offset = offset; - if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { - nextItem.m_methodDistance = 0f; - } else { - nextItem.m_methodDistance = methodDistance + distance; - } - - float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)laneIndex, curLaneId, nextLaneInfo); // NON-STOCK CODE - if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { - nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); - nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); - if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { - nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); - } else { - nextItem.m_direction = nextLaneInfo.m_finalDirection; - } - - if (nextLaneId == this.m_startLaneA) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < this.m_startOffsetA) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > this.m_startOffsetA)) { - return; - } - float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE - float nextOffsetDistance = (float)Mathf.Abs((int)nextItem.m_position.m_offset - (int)this.m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - nextItem.m_comparisonValue += nextOffsetDistance * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); - nextItem.m_duration += nextOffsetDistance * nextSegment.m_averageLength / nextSpeed; - } - - if (nextLaneId == this.m_startLaneB) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < this.m_startOffsetB) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > this.m_startOffsetB)) { - return; - } - float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE - float nextOffsetDistance = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - nextItem.m_comparisonValue += nextOffsetDistance * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); - nextItem.m_duration += nextOffsetDistance * nextSegment.m_averageLength / nextSpeed; - } - - nextItem.m_laneID = nextLaneId; - nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); - nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); - nextItem.m_trafficRand = 0; -#if DEBUGNEWPF - if (debug) { - m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); - } -#endif - this.AddBufferItem(nextItem, item.m_position); - } - } - return; - } - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - } - - private bool ProcessItemCosts(bool debug, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, /*SegmentRoutingData prevSegmentRouting,*/ ref NetSegment nextSegment, ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian, bool isMiddle) { - bool foundForced = false; - return ProcessItemCosts(debug, true, LaneChangingCostCalculationMode.None, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref nextSegment, /*routingManager.segmentRoutings[nextSegmentId],*/ ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian, null, null, null, null, null, isMiddle, out foundForced); - } - - // 3 - private bool ProcessItemCosts(bool debug, bool obeyStockLaneArrows, LaneChangingCostCalculationMode laneChangingCostCalculationMode, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, /* SegmentRoutingData prevSegmentRouting,*/ ref NetSegment nextSegment, /*SegmentRoutingData nextSegmentRouting,*/ ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian, int? forcedLaneIndex, uint? forcedLaneId, byte? forcedLaneDist, float? segmentSelectionCost, float? laneSelectionCost, bool isMiddle, out bool foundForced) { + nextItem.m_position.m_segment = nextSegmentId; + nextItem.m_position.m_lane = (byte)laneIndex; + nextItem.m_position.m_offset = offset; + if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { + nextItem.m_methodDistance = 0f; + } else { + nextItem.m_methodDistance = methodDistance + distance; + } + + float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)laneIndex, curLaneId, nextLaneInfo); // NON-STOCK CODE + if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { + nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); + nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); + if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { + nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); + } else { + nextItem.m_direction = nextLaneInfo.m_finalDirection; + } + + if (nextLaneId == this.m_startLaneA) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < this.m_startOffsetA) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > this.m_startOffsetA)) { + return; + } + float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE + float nextOffsetDistance = (float)Mathf.Abs((int)nextItem.m_position.m_offset - (int)this.m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + nextItem.m_comparisonValue += nextOffsetDistance * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); + nextItem.m_duration += nextOffsetDistance * nextSegment.m_averageLength / nextSpeed; + } + + if (nextLaneId == this.m_startLaneB) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < this.m_startOffsetB) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > this.m_startOffsetB)) { + return; + } + float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE + float nextOffsetDistance = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + nextItem.m_comparisonValue += nextOffsetDistance * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); + nextItem.m_duration += nextOffsetDistance * nextSegment.m_averageLength / nextSpeed; + } + + nextItem.m_laneID = nextLaneId; + nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); + nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); + nextItem.m_trafficRand = 0; +#if DEBUGNEWPF + if (debug) { + m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); + } +#endif + this.AddBufferItem(nextItem, item.m_position); + } + } + return; + } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + } + + private bool ProcessItemCosts(bool debug, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, /*SegmentRoutingData prevSegmentRouting,*/ ref NetSegment nextSegment, ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian, bool isMiddle) { + bool foundForced = false; + return ProcessItemCosts(debug, true, LaneChangingCostCalculationMode.None, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref nextSegment, /*routingManager.segmentRoutings[nextSegmentId],*/ ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian, null, null, null, null, null, isMiddle, out foundForced); + } + + // 3 + private bool ProcessItemCosts(bool debug, bool obeyStockLaneArrows, LaneChangingCostCalculationMode laneChangingCostCalculationMode, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, /* SegmentRoutingData prevSegmentRouting,*/ ref NetSegment nextSegment, /*SegmentRoutingData nextSegmentRouting,*/ ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian, int? forcedLaneIndex, uint? forcedLaneId, byte? forcedLaneDist, float? segmentSelectionCost, float? laneSelectionCost, bool isMiddle, out bool foundForced) { #if DEBUGNEWPF && DEBUG - debug = debug && m_conf.Debug.Switches[1]; + debug = debug && m_conf.Debug.Switches[1]; #else debug = false; #endif #if DEBUGNEWPF && DEBUG - List logBuf = null; - if (debug) - logBuf = new List(); + List logBuf = null; + if (debug) + logBuf = new List(); #endif - foundForced = false; - bool blocked = false; - if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { + foundForced = false; + bool blocked = false; + if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { #if DEBUGNEWPF - if (debug) { - logBuf.Add($"Segment is PathFailed or flooded: {nextSegment.m_flags}"); - logBuf.Add("-- method returns --"); - FlushCostLog(logBuf); - } + if (debug) { + logBuf.Add($"Segment is PathFailed or flooded: {nextSegment.m_flags}"); + logBuf.Add("-- method returns --"); + FlushCostLog(logBuf); + } #endif - return blocked; - } - NetManager netManager = Singleton.instance; + return blocked; + } + NetManager netManager = Singleton.instance; - NetInfo nextSegmentInfo = nextSegment.Info; - NetInfo prevSegmentInfo = prevSegment.Info; - int nextNumLanes = nextSegmentInfo.m_lanes.Length; - NetInfo.Direction nextDir = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; - NetInfo.Direction nextFinalDir = ((nextSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? nextDir : NetInfo.InvertDirection(nextDir); - float turningAngle = 1f; + NetInfo nextSegmentInfo = nextSegment.Info; + NetInfo prevSegmentInfo = prevSegment.Info; + int nextNumLanes = nextSegmentInfo.m_lanes.Length; + NetInfo.Direction nextDir = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; + NetInfo.Direction nextFinalDir = ((nextSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? nextDir : NetInfo.InvertDirection(nextDir); + float turningAngle = 1f; #if DEBUGNEWPF - if (debug) - logBuf.Add($"isStockLaneChangerUsed={Options.isStockLaneChangerUsed()}, _extVehicleType={queueItem.vehicleType}, nonBus={(queueItem.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) != ExtVehicleType.None}, _stablePath={m_stablePath}, enablePedestrian={enablePedestrian}, enableVehicle={enableVehicle}"); + if (debug) + logBuf.Add($"isStockLaneChangerUsed={Options.isStockLaneChangerUsed()}, _extVehicleType={queueItem.vehicleType}, nonBus={(queueItem.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) != ExtVehicleType.None}, _stablePath={m_stablePath}, enablePedestrian={enablePedestrian}, enableVehicle={enableVehicle}"); #endif - float prevMaxSpeed = 1f; - float prevLaneSpeed = 1f; - NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; - VehicleInfo.VehicleType prevVehicleType = VehicleInfo.VehicleType.None; - // NON-STOCK CODE START // - bool nextIsStartNodeOfPrevSegment = prevSegment.m_startNode == nextNodeId; - ushort sourceNodeId = nextIsStartNodeOfPrevSegment ? prevSegment.m_endNode : prevSegment.m_startNode; - bool prevIsJunction = (netManager.m_nodes.m_buffer[sourceNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; + float prevMaxSpeed = 1f; + float prevLaneSpeed = 1f; + NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; + VehicleInfo.VehicleType prevVehicleType = VehicleInfo.VehicleType.None; + // NON-STOCK CODE START // + bool nextIsStartNodeOfPrevSegment = prevSegment.m_startNode == nextNodeId; + ushort sourceNodeId = nextIsStartNodeOfPrevSegment ? prevSegment.m_endNode : prevSegment.m_startNode; + bool prevIsJunction = (netManager.m_nodes.m_buffer[sourceNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; #if COUNTSEGMENTSTONEXTJUNCTION bool prevIsRealJunction = prevIsJunction && (netManager.m_nodes.m_buffer[sourceNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); #endif - /*bool nextIsRealJunction = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction && - (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut);*/ - int prevOuterSimilarLaneIndex = -1; - NetInfo.Lane prevLaneInfo = null; - // NON-STOCK CODE END // - if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { - prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; - prevLaneType = prevLaneInfo.m_laneType; - prevVehicleType = prevLaneInfo.m_vehicleType; - prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // NON-STOCK CODE - prevLaneSpeed = this.CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE - - // NON-STOCK CODE START - if ((byte)(prevLaneInfo.m_direction & NetInfo.Direction.Forward) != 0) { - prevOuterSimilarLaneIndex = prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1; - } else { - prevOuterSimilarLaneIndex = prevLaneInfo.m_similarLaneIndex; - } - // NON-STOCK CODE END - } - - if (prevLaneType == NetInfo.LaneType.Vehicle && (prevVehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { - // check turning angle - - turningAngle = 0.01f - Mathf.Min(nextSegmentInfo.m_maxTurnAngleCos, prevSegmentInfo.m_maxTurnAngleCos); - if (turningAngle < 1f) { - Vector3 prevDirection; - if (nextNodeId == prevSegment.m_startNode) { - prevDirection = prevSegment.m_startDirection; - } else { - prevDirection = prevSegment.m_endDirection; - } - Vector3 nextDirection; - if ((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { - nextDirection = nextSegment.m_endDirection; - } else { - nextDirection = nextSegment.m_startDirection; - } - float dirDotProd = prevDirection.x * nextDirection.x + prevDirection.z * nextDirection.z; - if (dirDotProd >= turningAngle) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"turningAngle < 1f! dirDotProd={dirDotProd} >= turningAngle{turningAngle}!"); - logBuf.Add("-- method returns --"); - FlushCostLog(logBuf); - } -#endif - return blocked; - } - } - } - - float prevDist; - if (prevLaneType == NetInfo.LaneType.PublicTransport) { - prevDist = netManager.m_lanes.m_buffer[item.m_laneID].m_length; - } else { - prevDist = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); - } - - float prevCost = prevDist; - - -#if DEBUGNEWPF - float oldPrevCost = prevCost; -#endif - - // NON-STOCK CODE START - if (segmentSelectionCost != null) { - prevCost *= (float)segmentSelectionCost; - } - - if (laneSelectionCost != null) { - prevCost *= (float)laneSelectionCost; - } - // NON-STOCK CODE END - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied traffic cost factors:\n" + - "\t" + $"oldPrevCost={oldPrevCost}\n" + - "\t" + $"=> prevCost={prevCost}\n" - ); -#endif - - // stock code check for vehicle ban policies removed - // stock code for transport lane usage control removed - - // calculate ticket costs - float ticketCosts = 0f; - if (! this.m_ignoreCost) { - int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; - if (ticketCost != 0) { - ticketCosts += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; - } - } - - if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - float prevOffsetCost = (float)Mathf.Abs((int)(connectOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevCost; - float prevMethodDist = item.m_methodDistance + (float)Mathf.Abs((int)connectOffset - (int)item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevDist; - float prevDuration = item.m_duration + prevOffsetCost / prevMaxSpeed; - // NON-STOCK: vehicle restriction are applied to previous segment length in MainPathFind (not here, and not to prevOffsetCost) - float prevComparisonPlusOffsetCostOverSpeed = item.m_comparisonValue + prevOffsetCost / (prevLaneSpeed * this.m_maxLength); - - if (!m_stablePath) { - // CO randomization. Only randomizes over segments, not over lanes. - if (segmentSelectionCost == null) { // NON-STOCK CODE - Randomizer randomizer = new Randomizer(this.m_pathFindIndex << 16 | (uint)item.m_position.m_segment); - prevOffsetCost *= (float)(randomizer.Int32(900, 1000 + (int)(prevSegment.m_trafficDensity * 10)) + this.m_pathRandomizer.Int32(20u)) * 0.001f; - } - } - - Vector3 prevLaneConnectPos = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - int newLaneIndexFromInner = laneIndexFromInner; - bool transitionNode = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; - NetInfo.LaneType allowedLaneTypes = this.m_laneTypes; - VehicleInfo.VehicleType allowedVehicleTypes = this.m_vehicleTypes; + /*bool nextIsRealJunction = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction && + (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut);*/ + int prevOuterSimilarLaneIndex = -1; + NetInfo.Lane prevLaneInfo = null; + // NON-STOCK CODE END // + if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { + prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; + prevLaneType = prevLaneInfo.m_laneType; + prevVehicleType = prevLaneInfo.m_vehicleType; + prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // NON-STOCK CODE + prevLaneSpeed = this.CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE + + // NON-STOCK CODE START + if ((byte)(prevLaneInfo.m_direction & NetInfo.Direction.Forward) != 0) { + prevOuterSimilarLaneIndex = prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1; + } else { + prevOuterSimilarLaneIndex = prevLaneInfo.m_similarLaneIndex; + } + // NON-STOCK CODE END + } + + if (prevLaneType == NetInfo.LaneType.Vehicle && (prevVehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { + // check turning angle + + turningAngle = 0.01f - Mathf.Min(nextSegmentInfo.m_maxTurnAngleCos, prevSegmentInfo.m_maxTurnAngleCos); + if (turningAngle < 1f) { + Vector3 prevDirection; + if (nextNodeId == prevSegment.m_startNode) { + prevDirection = prevSegment.m_startDirection; + } else { + prevDirection = prevSegment.m_endDirection; + } + Vector3 nextDirection; + if ((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { + nextDirection = nextSegment.m_endDirection; + } else { + nextDirection = nextSegment.m_startDirection; + } + float dirDotProd = prevDirection.x * nextDirection.x + prevDirection.z * nextDirection.z; + if (dirDotProd >= turningAngle) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"turningAngle < 1f! dirDotProd={dirDotProd} >= turningAngle{turningAngle}!"); + logBuf.Add("-- method returns --"); + FlushCostLog(logBuf); + } +#endif + return blocked; + } + } + } + + float prevDist; + if (prevLaneType == NetInfo.LaneType.PublicTransport) { + prevDist = netManager.m_lanes.m_buffer[item.m_laneID].m_length; + } else { + prevDist = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); + } + + float prevCost = prevDist; + + +#if DEBUGNEWPF + float oldPrevCost = prevCost; +#endif + + // NON-STOCK CODE START + if (segmentSelectionCost != null) { + prevCost *= (float)segmentSelectionCost; + } + + if (laneSelectionCost != null) { + prevCost *= (float)laneSelectionCost; + } + // NON-STOCK CODE END + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied traffic cost factors:\n" + + "\t" + $"oldPrevCost={oldPrevCost}\n" + + "\t" + $"=> prevCost={prevCost}\n" + ); +#endif + + // stock code check for vehicle ban policies removed + // stock code for transport lane usage control removed + + // calculate ticket costs + float ticketCosts = 0f; + if (! this.m_ignoreCost) { + int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; + if (ticketCost != 0) { + ticketCosts += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; + } + } + + if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + float prevOffsetCost = (float)Mathf.Abs((int)(connectOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevCost; + float prevMethodDist = item.m_methodDistance + (float)Mathf.Abs((int)connectOffset - (int)item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevDist; + float prevDuration = item.m_duration + prevOffsetCost / prevMaxSpeed; + // NON-STOCK: vehicle restriction are applied to previous segment length in MainPathFind (not here, and not to prevOffsetCost) + float prevComparisonPlusOffsetCostOverSpeed = item.m_comparisonValue + prevOffsetCost / (prevLaneSpeed * this.m_maxLength); + + if (!m_stablePath) { + // CO randomization. Only randomizes over segments, not over lanes. + if (segmentSelectionCost == null) { // NON-STOCK CODE + Randomizer randomizer = new Randomizer(this.m_pathFindIndex << 16 | (uint)item.m_position.m_segment); + prevOffsetCost *= (float)(randomizer.Int32(900, 1000 + (int)(prevSegment.m_trafficDensity * 10)) + this.m_pathRandomizer.Int32(20u)) * 0.001f; + } + } + + Vector3 prevLaneConnectPos = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + int newLaneIndexFromInner = laneIndexFromInner; + bool transitionNode = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; + NetInfo.LaneType allowedLaneTypes = this.m_laneTypes; + VehicleInfo.VehicleType allowedVehicleTypes = this.m_vehicleTypes; + + if (!enableVehicle) { + allowedVehicleTypes &= VehicleInfo.VehicleType.Bicycle; + if (allowedVehicleTypes == VehicleInfo.VehicleType.None) { + allowedLaneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + } + if (!enablePedestrian) { + allowedLaneTypes &= ~NetInfo.LaneType.Pedestrian; + } + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"allowedVehicleTypes={allowedVehicleTypes} allowedLaneTypes={allowedLaneTypes}"); +#endif + + // NON-STOCK CODE START // + float laneChangeBaseCosts = 1f; + float junctionBaseCosts = 1f; + if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { + float rand = (float)this.m_pathRandomizer.Int32(101u) / 100f; + laneChangeBaseCosts = m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost + rand * (m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost - m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost); + if (prevIsJunction) { + junctionBaseCosts = m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost; + } + } - if (!enableVehicle) { - allowedVehicleTypes &= VehicleInfo.VehicleType.Bicycle; - if (allowedVehicleTypes == VehicleInfo.VehicleType.None) { - allowedLaneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - } - if (!enablePedestrian) { - allowedLaneTypes &= ~NetInfo.LaneType.Pedestrian; - } + // NON-STOCK CODE END // + uint laneIndex = forcedLaneIndex != null ? (uint)forcedLaneIndex : 0u; // NON-STOCK CODE, forcedLaneIndex is not null if the next node is a (real) junction + uint curLaneId = (uint)(forcedLaneId != null ? forcedLaneId : nextSegment.m_lanes); // NON-STOCK CODE, forceLaneId is not null if the next node is a (real) junction + while (laneIndex < nextNumLanes && curLaneId != 0u) { + // NON-STOCK CODE START // + if (forcedLaneIndex != null && laneIndex != forcedLaneIndex) { #if DEBUGNEWPF - if (debug) - logBuf.Add($"allowedVehicleTypes={allowedVehicleTypes} allowedLaneTypes={allowedLaneTypes}"); + if (debug) + logBuf.Add($"forceLaneIndex break! laneIndex={laneIndex}"); #endif + break; + } + // NON-STOCK CODE END // + NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[laneIndex]; - // NON-STOCK CODE START // - float laneChangeBaseCosts = 1f; - float junctionBaseCosts = 1f; - if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { - float rand = (float)this.m_pathRandomizer.Int32(101u) / 100f; - laneChangeBaseCosts = m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost + rand * (m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost - m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost); - if (prevIsJunction) { - junctionBaseCosts = m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost; - } - } - - // NON-STOCK CODE END // - - uint laneIndex = forcedLaneIndex != null ? (uint)forcedLaneIndex : 0u; // NON-STOCK CODE, forcedLaneIndex is not null if the next node is a (real) junction - uint curLaneId = (uint)(forcedLaneId != null ? forcedLaneId : nextSegment.m_lanes); // NON-STOCK CODE, forceLaneId is not null if the next node is a (real) junction - while (laneIndex < nextNumLanes && curLaneId != 0u) { - // NON-STOCK CODE START // - if (forcedLaneIndex != null && laneIndex != forcedLaneIndex) { -#if DEBUGNEWPF - if (debug) - logBuf.Add($"forceLaneIndex break! laneIndex={laneIndex}"); -#endif - break; - } - // NON-STOCK CODE END // - NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[laneIndex]; - - if ((byte)(nextLaneInfo.m_finalDirection & nextFinalDir) != 0) { - // lane direction is compatible -#if DEBUGNEWPF - if (debug) - logBuf.Add($"Lane direction check passed: {nextLaneInfo.m_finalDirection}"); -#endif - if (nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes) && - (nextSegmentId != item.m_position.m_segment || laneIndex != (int)item.m_position.m_lane)) { - // vehicle types match and no u-turn to the previous lane - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"vehicle type check passed: {nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes)} && {(nextSegmentId != item.m_position.m_segment || laneIndex != (int)item.m_position.m_lane)}"); -#endif - - // NON-STOCK CODE START // - float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)laneIndex, curLaneId, nextLaneInfo); - float customDeltaCost = 0f; - // NON-STOCK CODE END // - - Vector3 nextLaneEndPointPos; - if ((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { - nextLaneEndPointPos = netManager.m_lanes.m_buffer[curLaneId].m_bezier.d; - } else { - nextLaneEndPointPos = netManager.m_lanes.m_buffer[curLaneId].m_bezier.a; - } - float transitionCost = Vector3.Distance(nextLaneEndPointPos, prevLaneConnectPos); // This gives the distance of the previous to next lane endpoints. - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"costs from {nextSegmentId} (off {(byte)(((nextDir & NetInfo.Direction.Forward) == 0) ? 0 : 255)}) to {item.m_position.m_segment} (off {item.m_position.m_offset}), connectOffset={connectOffset}: transitionCost={transitionCost}"); -#endif - - if (transitionNode) { - transitionCost *= 2f; - } - - BufferItem nextItem; + if ((byte)(nextLaneInfo.m_finalDirection & nextFinalDir) != 0) { + // lane direction is compatible +#if DEBUGNEWPF + if (debug) + logBuf.Add($"Lane direction check passed: {nextLaneInfo.m_finalDirection}"); +#endif + if (nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes) && + (nextSegmentId != item.m_position.m_segment || laneIndex != (int)item.m_position.m_lane)) { + // vehicle types match and no u-turn to the previous lane + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"vehicle type check passed: {nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes)} && {(nextSegmentId != item.m_position.m_segment || laneIndex != (int)item.m_position.m_lane)}"); +#endif + + // NON-STOCK CODE START // + float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)laneIndex, curLaneId, nextLaneInfo); + float customDeltaCost = 0f; + // NON-STOCK CODE END // + + Vector3 nextLaneEndPointPos; + if ((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { + nextLaneEndPointPos = netManager.m_lanes.m_buffer[curLaneId].m_bezier.d; + } else { + nextLaneEndPointPos = netManager.m_lanes.m_buffer[curLaneId].m_bezier.a; + } + float transitionCost = Vector3.Distance(nextLaneEndPointPos, prevLaneConnectPos); // This gives the distance of the previous to next lane endpoints. + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"costs from {nextSegmentId} (off {(byte)(((nextDir & NetInfo.Direction.Forward) == 0) ? 0 : 255)}) to {item.m_position.m_segment} (off {item.m_position.m_offset}), connectOffset={connectOffset}: transitionCost={transitionCost}"); +#endif + + if (transitionNode) { + transitionCost *= 2f; + } + + BufferItem nextItem; #if COUNTSEGMENTSTONEXTJUNCTION // NON-STOCK CODE START // if (prevIsRealJunction) { @@ -2048,200 +2053,200 @@ private bool ProcessItemCosts(bool debug, bool obeyStockLaneArrows, LaneChanging } // NON-STOCK CODE END // #endif - nextItem.m_comparisonValue = ticketCosts; - nextItem.m_position.m_segment = nextSegmentId; - nextItem.m_position.m_lane = (byte)laneIndex; - nextItem.m_position.m_offset = (byte)(((nextDir & NetInfo.Direction.Forward) == 0) ? 0 : 255); - if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { - nextItem.m_methodDistance = 0f; - // NON-STOCK CODE START - if (Options.realisticPublicTransport && isMiddle && nextLaneInfo.m_laneType == NetInfo.LaneType.PublicTransport && (item.m_lanesUsed & NetInfo.LaneType.PublicTransport) != NetInfo.LaneType.None) { - // apply penalty when switching public transport vehicles - float transportTransitionPenalty = (m_conf.PathFinding.PublicTransportTransitionMinPenalty + ((float)netManager.m_nodes.m_buffer[nextNodeId].m_maxWaitTime * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR) * (m_conf.PathFinding.PublicTransportTransitionMaxPenalty - m_conf.PathFinding.PublicTransportTransitionMinPenalty)) / (0.25f * this.m_maxLength); -#if DEBUGNEWPF - if (debug) - logBuf.Add($"applying public transport transition penalty: {transportTransitionPenalty}"); -#endif - nextItem.m_comparisonValue += transportTransitionPenalty; - } - // NON-STOCK CODE END - } else { - nextItem.m_methodDistance = prevMethodDist + transitionCost; - } + nextItem.m_comparisonValue = ticketCosts; + nextItem.m_position.m_segment = nextSegmentId; + nextItem.m_position.m_lane = (byte)laneIndex; + nextItem.m_position.m_offset = (byte)(((nextDir & NetInfo.Direction.Forward) == 0) ? 0 : 255); + if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { + nextItem.m_methodDistance = 0f; + // NON-STOCK CODE START + if (Options.realisticPublicTransport && isMiddle && nextLaneInfo.m_laneType == NetInfo.LaneType.PublicTransport && (item.m_lanesUsed & NetInfo.LaneType.PublicTransport) != NetInfo.LaneType.None) { + // apply penalty when switching public transport vehicles + float transportTransitionPenalty = (m_conf.PathFinding.PublicTransportTransitionMinPenalty + ((float)netManager.m_nodes.m_buffer[nextNodeId].m_maxWaitTime * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR) * (m_conf.PathFinding.PublicTransportTransitionMaxPenalty - m_conf.PathFinding.PublicTransportTransitionMinPenalty)) / (0.25f * this.m_maxLength); +#if DEBUGNEWPF + if (debug) + logBuf.Add($"applying public transport transition penalty: {transportTransitionPenalty}"); +#endif + nextItem.m_comparisonValue += transportTransitionPenalty; + } + // NON-STOCK CODE END + } else { + nextItem.m_methodDistance = prevMethodDist + transitionCost; + } #if DEBUGNEWPF - if (debug) - logBuf.Add($"checking if methodDistance is in range: {nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian} || {nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance} ({nextItem.m_methodDistance})"); + if (debug) + logBuf.Add($"checking if methodDistance is in range: {nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian} || {nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance} ({nextItem.m_methodDistance})"); #endif - if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { + if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { - // NON-STOCK CODE START // - if (laneChangingCostCalculationMode == LaneChangingCostCalculationMode.None) { - float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); - nextItem.m_comparisonValue += prevComparisonPlusOffsetCostOverSpeed + transitionCostOverMeanMaxSpeed; // stock code - } else { - nextItem.m_comparisonValue += item.m_comparisonValue; - customDeltaCost = transitionCost + prevOffsetCost; // customDeltaCost now holds the costs for driving on the segment + costs for changing the segment + // NON-STOCK CODE START // + if (laneChangingCostCalculationMode == LaneChangingCostCalculationMode.None) { + float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); + nextItem.m_comparisonValue += prevComparisonPlusOffsetCostOverSpeed + transitionCostOverMeanMaxSpeed; // stock code + } else { + nextItem.m_comparisonValue += item.m_comparisonValue; + customDeltaCost = transitionCost + prevOffsetCost; // customDeltaCost now holds the costs for driving on the segment + costs for changing the segment #if DEBUGNEWPF - if (debug) { - logBuf.Add($"Path from {nextSegmentId} (idx {laneIndex}, id {curLaneId}) to {item.m_position.m_segment} (lane {prevOuterSimilarLaneIndex} from outer, idx {item.m_position.m_lane}): laneChangingCostCalculationMode={laneChangingCostCalculationMode}, transitionCost={transitionCost}"); - } + if (debug) { + logBuf.Add($"Path from {nextSegmentId} (idx {laneIndex}, id {curLaneId}) to {item.m_position.m_segment} (lane {prevOuterSimilarLaneIndex} from outer, idx {item.m_position.m_lane}): laneChangingCostCalculationMode={laneChangingCostCalculationMode}, transitionCost={transitionCost}"); + } #endif - } - nextItem.m_duration = prevDuration + transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); + } + nextItem.m_duration = prevDuration + transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); - // account for public tranport transition costs on non-PT paths - if ( + // account for public tranport transition costs on non-PT paths + if ( #if DEBUG - !m_conf.Debug.Switches[20] && + !m_conf.Debug.Switches[20] && #endif - Options.realisticPublicTransport && - (curLaneId == this.m_startLaneA || curLaneId == this.m_startLaneB) && - (item.m_lanesUsed & (NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport)) == NetInfo.LaneType.Pedestrian) { - float transportTransitionPenalty = (2f * m_conf.PathFinding.PublicTransportTransitionMaxPenalty) / (0.25f * this.m_maxLength); + Options.realisticPublicTransport && + (curLaneId == this.m_startLaneA || curLaneId == this.m_startLaneB) && + (item.m_lanesUsed & (NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport)) == NetInfo.LaneType.Pedestrian) { + float transportTransitionPenalty = (2f * m_conf.PathFinding.PublicTransportTransitionMaxPenalty) / (0.25f * this.m_maxLength); #if DEBUGNEWPF - if (debug) - logBuf.Add($"applying public transport transition penalty on non-PT path: {transportTransitionPenalty}"); + if (debug) + logBuf.Add($"applying public transport transition penalty on non-PT path: {transportTransitionPenalty}"); #endif - nextItem.m_comparisonValue += transportTransitionPenalty; - } - // NON-STOCK CODE END // + nextItem.m_comparisonValue += transportTransitionPenalty; + } + // NON-STOCK CODE END // - nextItem.m_direction = nextDir; - if (curLaneId == this.m_startLaneA) { - if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetA) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetA)) { + nextItem.m_direction = nextDir; + if (curLaneId == this.m_startLaneA) { + if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetA) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetA)) { #if DEBUGNEWPF - if (debug) - logBuf.Add($"Current lane is start lane A. goto next lane"); -#endif - goto CONTINUE_LANE_LOOP; - } - float nextLaneSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE - float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetA)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - float nextSegLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, nextSegment.m_averageLength); - nextItem.m_comparisonValue += nextOffset * nextSegLength / (nextLaneSpeed * this.m_maxLength); - nextItem.m_duration += nextOffset * nextSegLength / nextLaneSpeed; - } - - if (curLaneId == this.m_startLaneB) { - if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetB) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetB)) { -#if DEBUGNEWPF - if (debug) - logBuf.Add($"Current lane is start lane B. goto next lane"); -#endif - goto CONTINUE_LANE_LOOP; - } - float nextLaneSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE - float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - float nextSegLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, nextSegment.m_averageLength); - nextItem.m_comparisonValue += nextOffset * nextSegLength / (nextLaneSpeed * this.m_maxLength); - nextItem.m_duration += nextOffset * nextSegLength / nextLaneSpeed; - } - - if (!this.m_ignoreBlocked && (nextSegment.m_flags & NetSegment.Flags.Blocked) != NetSegment.Flags.None && - (byte)(nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { - // NON-STOCK CODE START // - if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { -#if DEBUGNEWPF - float oldCustomDeltaCost = customDeltaCost; -#endif - // apply vanilla game restriction policies - customDeltaCost *= 10f; -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - "\t" + $"applied blocked road cost factor on activated AI:\n" + - "\t" + $"oldCustomDeltaCost={oldCustomDeltaCost}\n" + - "\t" + $"=> customDeltaCost={customDeltaCost}\n" - ); -#endif - } else { - // NON-STOCK CODE END // -#if DEBUGNEWPF - if (debug) - logBuf.Add($"Applying blocked road cost factor on disabled advanced AI"); -#endif - - nextItem.m_comparisonValue += 0.1f; - } - blocked = true; - } - - if ((byte)(nextLaneInfo.m_laneType & prevLaneType) != 0 && nextLaneInfo.m_vehicleType == prevVehicleType) { -#if DEBUGNEWPF - if (debug) - logBuf.Add($"Applying stock lane changing costs. obeyStockLaneArrows={obeyStockLaneArrows}"); -#endif - - - if (obeyStockLaneArrows) { - // this is CO's way of matching lanes between segments - int firstTarget = (int)netManager.m_lanes.m_buffer[curLaneId].m_firstTarget; - int lastTarget = (int)netManager.m_lanes.m_buffer[curLaneId].m_lastTarget; - if (laneIndexFromInner < firstTarget || laneIndexFromInner >= lastTarget) { - nextItem.m_comparisonValue += Mathf.Max(1f, transitionCost * 3f - 3f) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); - } - } // NON-STOCK CODE - - // stock code that prohibits cars to be on public transport lanes removed - /*if (!this._transportVehicle && nextLaneInfo.m_laneType == NetInfo.LaneType.TransportVehicle) { - nextItem.m_comparisonValue += 20f / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this._maxLength); - }*/ - - } - - // NON-STOCK CODE START // - bool addItem = true; // should we add the next item to the buffer? - if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { - // Advanced AI cost calculation - -#if DEBUGNEWPF - if (debug) - logBuf.Add($"Calculating advanced AI lane changing costs"); -#endif - - int laneDist; // absolute lane distance - if (laneChangingCostCalculationMode == LaneChangingCostCalculationMode.ByGivenDistance && forcedLaneDist != null) { - laneDist = (byte)forcedLaneDist; - } else { - int nextOuterSimilarLaneIndex; - if ((byte)(nextLaneInfo.m_direction & NetInfo.Direction.Forward) != 0) { - nextOuterSimilarLaneIndex = nextLaneInfo.m_similarLaneCount - nextLaneInfo.m_similarLaneIndex - 1; - } else { - nextOuterSimilarLaneIndex = nextLaneInfo.m_similarLaneIndex; - } - - int relLaneDist = nextOuterSimilarLaneIndex - prevOuterSimilarLaneIndex; // relative lane distance (positive: change to more outer lane, negative: change to more inner lane) - laneDist = Math.Abs(relLaneDist); - } - - // apply lane changing costs - float laneMetric = 1f; - bool relaxedLaneChanging = queueItem.vehicleId == 0 && (curLaneId == m_startLaneA || curLaneId == m_startLaneB); - if (laneDist > 0 && !relaxedLaneChanging) { - laneMetric = 1f + laneDist * - junctionBaseCosts * - laneChangeBaseCosts * // road type based lane changing cost factor - (laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f); // additional costs for changing multiple lanes at once - } - - // on highways: avoid lane changing before junctions: multiply with inverted distance to next junction - /*float junctionMetric = 1f; - if (prevSegmentRouting.highway && nextSegmentRouting.highway && // we are on a highway road - !nextIsRealJunction && // next is not a junction - laneDist > 0) { - uint dist = _pathRandomizer.UInt32(_conf.MinHighwayInterchangeSegments, (_conf.MaxHighwayInterchangeSegments >= _conf.MinHighwayInterchangeSegments ? _conf.MaxHighwayInterchangeSegments : _conf.MinHighwayInterchangeSegments) + 1u); - if (nextItem.m_numSegmentsToNextJunction < dist) { - junctionMetric = _conf.HighwayInterchangeLaneChangingBaseCost * (float)(dist - nextItem.m_numSegmentsToNextJunction); - } - }*/ - - // total metric value - float metric = laneMetric/* * junctionMetric*/; - - //float oldTransitionDistanceOverMaxSpeed = transitionCostOverMeanMaxSpeed; - float finalDeltaCost = (metric * customDeltaCost) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); + if (debug) + logBuf.Add($"Current lane is start lane A. goto next lane"); +#endif + goto CONTINUE_LANE_LOOP; + } + float nextLaneSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE + float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetA)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + float nextSegLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, nextSegment.m_averageLength); + nextItem.m_comparisonValue += nextOffset * nextSegLength / (nextLaneSpeed * this.m_maxLength); + nextItem.m_duration += nextOffset * nextSegLength / nextLaneSpeed; + } + + if (curLaneId == this.m_startLaneB) { + if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetB) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetB)) { +#if DEBUGNEWPF + if (debug) + logBuf.Add($"Current lane is start lane B. goto next lane"); +#endif + goto CONTINUE_LANE_LOOP; + } + float nextLaneSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE + float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + float nextSegLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, nextSegment.m_averageLength); + nextItem.m_comparisonValue += nextOffset * nextSegLength / (nextLaneSpeed * this.m_maxLength); + nextItem.m_duration += nextOffset * nextSegLength / nextLaneSpeed; + } + + if (!this.m_ignoreBlocked && (nextSegment.m_flags & NetSegment.Flags.Blocked) != NetSegment.Flags.None && + (byte)(nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { + // NON-STOCK CODE START // + if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { +#if DEBUGNEWPF + float oldCustomDeltaCost = customDeltaCost; +#endif + // apply vanilla game restriction policies + customDeltaCost *= 10f; +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + "\t" + $"applied blocked road cost factor on activated AI:\n" + + "\t" + $"oldCustomDeltaCost={oldCustomDeltaCost}\n" + + "\t" + $"=> customDeltaCost={customDeltaCost}\n" + ); +#endif + } else { + // NON-STOCK CODE END // +#if DEBUGNEWPF + if (debug) + logBuf.Add($"Applying blocked road cost factor on disabled advanced AI"); +#endif + + nextItem.m_comparisonValue += 0.1f; + } + blocked = true; + } + + if ((byte)(nextLaneInfo.m_laneType & prevLaneType) != 0 && nextLaneInfo.m_vehicleType == prevVehicleType) { +#if DEBUGNEWPF + if (debug) + logBuf.Add($"Applying stock lane changing costs. obeyStockLaneArrows={obeyStockLaneArrows}"); +#endif + + + if (obeyStockLaneArrows) { + // this is CO's way of matching lanes between segments + int firstTarget = (int)netManager.m_lanes.m_buffer[curLaneId].m_firstTarget; + int lastTarget = (int)netManager.m_lanes.m_buffer[curLaneId].m_lastTarget; + if (laneIndexFromInner < firstTarget || laneIndexFromInner >= lastTarget) { + nextItem.m_comparisonValue += Mathf.Max(1f, transitionCost * 3f - 3f) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); + } + } // NON-STOCK CODE + + // stock code that prohibits cars to be on public transport lanes removed + /*if (!this._transportVehicle && nextLaneInfo.m_laneType == NetInfo.LaneType.TransportVehicle) { + nextItem.m_comparisonValue += 20f / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this._maxLength); + }*/ + + } + + // NON-STOCK CODE START // + bool addItem = true; // should we add the next item to the buffer? + if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { + // Advanced AI cost calculation + +#if DEBUGNEWPF + if (debug) + logBuf.Add($"Calculating advanced AI lane changing costs"); +#endif + + int laneDist; // absolute lane distance + if (laneChangingCostCalculationMode == LaneChangingCostCalculationMode.ByGivenDistance && forcedLaneDist != null) { + laneDist = (byte)forcedLaneDist; + } else { + int nextOuterSimilarLaneIndex; + if ((byte)(nextLaneInfo.m_direction & NetInfo.Direction.Forward) != 0) { + nextOuterSimilarLaneIndex = nextLaneInfo.m_similarLaneCount - nextLaneInfo.m_similarLaneIndex - 1; + } else { + nextOuterSimilarLaneIndex = nextLaneInfo.m_similarLaneIndex; + } + + int relLaneDist = nextOuterSimilarLaneIndex - prevOuterSimilarLaneIndex; // relative lane distance (positive: change to more outer lane, negative: change to more inner lane) + laneDist = Math.Abs(relLaneDist); + } + + // apply lane changing costs + float laneMetric = 1f; + bool relaxedLaneChanging = queueItem.vehicleId == 0 && (curLaneId == m_startLaneA || curLaneId == m_startLaneB); + if (laneDist > 0 && !relaxedLaneChanging) { + laneMetric = 1f + laneDist * + junctionBaseCosts * + laneChangeBaseCosts * // road type based lane changing cost factor + (laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f); // additional costs for changing multiple lanes at once + } + + // on highways: avoid lane changing before junctions: multiply with inverted distance to next junction + /*float junctionMetric = 1f; + if (prevSegmentRouting.highway && nextSegmentRouting.highway && // we are on a highway road + !nextIsRealJunction && // next is not a junction + laneDist > 0) { + uint dist = _pathRandomizer.UInt32(_conf.MinHighwayInterchangeSegments, (_conf.MaxHighwayInterchangeSegments >= _conf.MinHighwayInterchangeSegments ? _conf.MaxHighwayInterchangeSegments : _conf.MinHighwayInterchangeSegments) + 1u); + if (nextItem.m_numSegmentsToNextJunction < dist) { + junctionMetric = _conf.HighwayInterchangeLaneChangingBaseCost * (float)(dist - nextItem.m_numSegmentsToNextJunction); + } + }*/ + + // total metric value + float metric = laneMetric/* * junctionMetric*/; + + //float oldTransitionDistanceOverMaxSpeed = transitionCostOverMeanMaxSpeed; + float finalDeltaCost = (metric * customDeltaCost) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); // if (finalDeltaCost < 0f) { // // should never happen @@ -2261,245 +2266,245 @@ private bool ProcessItemCosts(bool debug, bool obeyStockLaneArrows, LaneChanging // finalDeltaCost = oldTransitionDistanceOverMaxSpeed; // } - nextItem.m_comparisonValue += finalDeltaCost; - - if (nextItem.m_comparisonValue > 1f) { - // comparison value got too big. Do not add the lane to the buffer - addItem = false; - } -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - $"-> TRANSIT to seg. {nextSegmentId}, lane {laneIndex}\n" + - "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + - "\t" + $"nextMaxSpeed={nextMaxSpeed}\n\n" + - "\t" + $"laneChangingCostCalculationMode={laneChangingCostCalculationMode}\n\n" + - "\t" + $"laneDist={laneDist}\n\n" + - "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + - "\t" + $"laneChangeRoadBaseCost={laneChangeBaseCosts}\n" + - "\t" + $"moreThanOneLaneCost={(laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f)}\n" + - "\t" + $"=> laneMetric={laneMetric}\n" + - //"\t" + $"=> junctionMetric={junctionMetric}\n\n" + - "\t" + $"=> metric={metric}\n" + - "\t" + $"deltaCostOverMeanMaxSpeed={finalDeltaCost}\n" + - "\t" + $"nextItem.m_comparisonValue={nextItem.m_comparisonValue}\n\n" + - "\t" + $"=> addItem={addItem}\n" - ); -#endif - } - - if (forcedLaneIndex != null && laneIndex == forcedLaneIndex && addItem) { - foundForced = true; - } - - if (addItem) { - // NON-STOCK CODE END // - - nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); - nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); - nextItem.m_laneID = curLaneId; - nextItem.m_trafficRand = item.m_trafficRand; -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"adding item: seg {nextItem.m_position.m_segment}, lane {nextItem.m_position.m_lane} (idx {nextItem.m_laneID}), off {nextItem.m_position.m_offset} -> seg {item.m_position.m_segment}, lane {item.m_position.m_lane} (idx {item.m_laneID}), off {item.m_position.m_offset}, cost {nextItem.m_comparisonValue}, previous cost {item.m_comparisonValue}, methodDist {nextItem.m_methodDistance}"); - m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); - } -#endif - - this.AddBufferItem(nextItem, item.m_position); - // NON-STOCK CODE START // - } else { -#if DEBUGNEWPF - if (debug) - logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + - $"-> item seg. {nextSegmentId}, lane {laneIndex} NOT ADDED\n" - ); -#endif - } - // NON-STOCK CODE END // - } - } - goto CONTINUE_LANE_LOOP; - } - - if ((byte)(nextLaneInfo.m_laneType & prevLaneType) != 0 && (nextLaneInfo.m_vehicleType & prevVehicleType) != VehicleInfo.VehicleType.None) { - ++newLaneIndexFromInner; - } - - CONTINUE_LANE_LOOP: - - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - continue; - } // foreach lane - laneIndexFromInner = newLaneIndexFromInner; - -#if DEBUGNEWPF - if (debug) { - logBuf.Add("-- method returns --"); - FlushCostLog(logBuf); - } -#endif - return blocked; - } - -#if DEBUGNEWPF - private void FlushCostLog(List logBuf) { - if (logBuf == null) - return; - - foreach (String toLog in logBuf) { - Log._Debug($"Pathfinder ({this.m_pathFindIndex}) for unit {Calculating} *COSTS*: " + toLog); - } - logBuf.Clear(); - } - - private void FlushMainLog(List logBuf, uint unitId) { - if (logBuf == null) - return; - - foreach (String toLog in logBuf) { - Log._Debug($"Pathfinder ({this.m_pathFindIndex}) for unit {Calculating} *MAIN*: " + toLog); - } - logBuf.Clear(); - } -#endif - - // 4 - private void ProcessItemPedBicycle(bool debug, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, ref NetSegment nextSegment, byte connectOffset, byte laneSwitchOffset, int nextLaneIndex, uint nextLaneId) { + nextItem.m_comparisonValue += finalDeltaCost; + + if (nextItem.m_comparisonValue > 1f) { + // comparison value got too big. Do not add the lane to the buffer + addItem = false; + } +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + $"-> TRANSIT to seg. {nextSegmentId}, lane {laneIndex}\n" + + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + + "\t" + $"nextMaxSpeed={nextMaxSpeed}\n\n" + + "\t" + $"laneChangingCostCalculationMode={laneChangingCostCalculationMode}\n\n" + + "\t" + $"laneDist={laneDist}\n\n" + + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + + "\t" + $"laneChangeRoadBaseCost={laneChangeBaseCosts}\n" + + "\t" + $"moreThanOneLaneCost={(laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f)}\n" + + "\t" + $"=> laneMetric={laneMetric}\n" + + //"\t" + $"=> junctionMetric={junctionMetric}\n\n" + + "\t" + $"=> metric={metric}\n" + + "\t" + $"deltaCostOverMeanMaxSpeed={finalDeltaCost}\n" + + "\t" + $"nextItem.m_comparisonValue={nextItem.m_comparisonValue}\n\n" + + "\t" + $"=> addItem={addItem}\n" + ); +#endif + } + + if (forcedLaneIndex != null && laneIndex == forcedLaneIndex && addItem) { + foundForced = true; + } + + if (addItem) { + // NON-STOCK CODE END // + + nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); + nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); + nextItem.m_laneID = curLaneId; + nextItem.m_trafficRand = item.m_trafficRand; +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"adding item: seg {nextItem.m_position.m_segment}, lane {nextItem.m_position.m_lane} (idx {nextItem.m_laneID}), off {nextItem.m_position.m_offset} -> seg {item.m_position.m_segment}, lane {item.m_position.m_lane} (idx {item.m_laneID}), off {item.m_position.m_offset}, cost {nextItem.m_comparisonValue}, previous cost {item.m_comparisonValue}, methodDist {nextItem.m_methodDistance}"); + m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); + } +#endif + + this.AddBufferItem(nextItem, item.m_position); + // NON-STOCK CODE START // + } else { +#if DEBUGNEWPF + if (debug) + logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + + $"-> item seg. {nextSegmentId}, lane {laneIndex} NOT ADDED\n" + ); +#endif + } + // NON-STOCK CODE END // + } + } + goto CONTINUE_LANE_LOOP; + } + + if ((byte)(nextLaneInfo.m_laneType & prevLaneType) != 0 && (nextLaneInfo.m_vehicleType & prevVehicleType) != VehicleInfo.VehicleType.None) { + ++newLaneIndexFromInner; + } + + CONTINUE_LANE_LOOP: + + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + continue; + } // foreach lane + laneIndexFromInner = newLaneIndexFromInner; + +#if DEBUGNEWPF + if (debug) { + logBuf.Add("-- method returns --"); + FlushCostLog(logBuf); + } +#endif + return blocked; + } + +#if DEBUGNEWPF + private void FlushCostLog(List logBuf) { + if (logBuf == null) + return; + + foreach (String toLog in logBuf) { + Log._Debug($"Pathfinder ({this.m_pathFindIndex}) for unit {Calculating} *COSTS*: " + toLog); + } + logBuf.Clear(); + } + + private void FlushMainLog(List logBuf, uint unitId) { + if (logBuf == null) + return; + + foreach (String toLog in logBuf) { + Log._Debug($"Pathfinder ({this.m_pathFindIndex}) for unit {Calculating} *MAIN*: " + toLog); + } + logBuf.Clear(); + } +#endif + + // 4 + private void ProcessItemPedBicycle(bool debug, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, ref NetSegment nextSegment, byte connectOffset, byte laneSwitchOffset, int nextLaneIndex, uint nextLaneId) { #if DEBUGNEWPF && DEBUG - List logBuf = null; - if (debug) { - logBuf = new List(); - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: exploring\n" + - "\t" + $"nextSegmentId={nextSegmentId}\n" + - "\t" + $"connectOffset={connectOffset}\n" + - "\t" + $"laneSwitchOffset={laneSwitchOffset}\n" + - "\t" + $"nextLaneIndex={nextLaneIndex}\n" + - "\t" + $"nextLaneId={nextLaneId}\n"); - } -#endif - - if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- next segment disabled mask is incompatible!\n" + - "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n" + - "\t" + $"_disableMask={m_disableMask}\n"); - FlushCostLog(logBuf); - } -#endif - return; - } - - NetManager netManager = Singleton.instance; - - // NON-STOCK CODE START - bool nextIsRegularNode = nextNodeId == nextSegment.m_startNode || nextNodeId == nextSegment.m_endNode; + List logBuf = null; + if (debug) { + logBuf = new List(); + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: exploring\n" + + "\t" + $"nextSegmentId={nextSegmentId}\n" + + "\t" + $"connectOffset={connectOffset}\n" + + "\t" + $"laneSwitchOffset={laneSwitchOffset}\n" + + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + + "\t" + $"nextLaneId={nextLaneId}\n"); + } +#endif + + if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- next segment disabled mask is incompatible!\n" + + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n" + + "\t" + $"_disableMask={m_disableMask}\n"); + FlushCostLog(logBuf); + } +#endif + return; + } + + NetManager netManager = Singleton.instance; + + // NON-STOCK CODE START + bool nextIsRegularNode = nextNodeId == nextSegment.m_startNode || nextNodeId == nextSegment.m_endNode; #if COUNTSEGMENTSTONEXTJUNCTION bool prevIsRealJunction = false; #endif - if (nextIsRegularNode) { - bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; - ushort prevNodeId = (nextNodeId == prevSegment.m_startNode) ? prevSegment.m_endNode : prevSegment.m_startNode; + if (nextIsRegularNode) { + bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; + ushort prevNodeId = (nextNodeId == prevSegment.m_startNode) ? prevSegment.m_endNode : prevSegment.m_startNode; #if COUNTSEGMENTSTONEXTJUNCTION prevIsRealJunction = (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.Junction && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); #endif - // check if pedestrians are not allowed to cross here - if (!junctionManager.IsPedestrianCrossingAllowed(nextSegmentId, nextIsStartNode)) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- pedestrian crossing not allowed!\n"); - FlushCostLog(logBuf); - } -#endif - return; - } - - // check if pedestrian light won't change to green - ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(nextSegmentId, nextIsStartNode, false); - if (lights != null) { - if (lights.InvalidPedestrianLight) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- invalid pedestrian lights!\n"); - FlushCostLog(logBuf); - } -#endif - return; - } - } - } - // NON-STOCK CODE END - - // NON-STOCK CODE START - /*if (!_allowEscapeTransport) { - ushort transportLineId = netManager.m_nodes.m_buffer[targetNodeId].m_transportLine; - if (transportLineId != 0 && Singleton.instance.m_lines.m_buffer[transportLineId].Info.m_transportType == TransportInfo.TransportType.EvacuationBus) - return; - }*/ - // NON-STOCK CODE END - NetInfo nextSegmentInfo = nextSegment.Info; - NetInfo prevSegmentInfo = prevSegment.Info; - int numLanes = nextSegmentInfo.m_lanes.Length; - float distance; - byte offset; - Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - if (nextSegmentId == item.m_position.m_segment) { - // next segment is previous segment - Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - distance = Vector3.Distance(a, b); - offset = connectOffset; - } else { - // next segment differs from previous segment - NetInfo.Direction direction = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; - Vector3 a; - if ((byte)(direction & NetInfo.Direction.Forward) != 0) { - a = netManager.m_lanes.m_buffer[nextLaneId].m_bezier.d; - } else { - a = netManager.m_lanes.m_buffer[nextLaneId].m_bezier.a; - } - distance = Vector3.Distance(a, b); - offset = (byte)(((direction & NetInfo.Direction.Forward) == 0) ? 0 : 255); - } - float prevMaxSpeed = 1f; - float prevSpeed = 1f; - NetInfo.LaneType laneType = NetInfo.LaneType.None; - VehicleInfo.VehicleType vehicleType = VehicleInfo.VehicleType.None; // NON-STOCK CODE - if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { - NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; - prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // SpeedLimitManager.GetLockFreeGameSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, ref lane2); // NON-STOCK CODE - laneType = prevLaneInfo.m_laneType; - vehicleType = prevLaneInfo.m_vehicleType; // NON-STOCK CODE - if ((byte)(laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { - laneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - prevSpeed = this.CalculateLaneSpeed(prevMaxSpeed, laneSwitchOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE - } - - float segLength; - if (laneType == NetInfo.LaneType.PublicTransport) { - segLength = netManager.m_lanes.m_buffer[item.m_laneID].m_length; - } else { - segLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); - } - float offsetLength = (float)Mathf.Abs((int)(laneSwitchOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * segLength; - float methodDistance = item.m_methodDistance + offsetLength; - float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * this.m_maxLength); - float duration = item.m_duration + offsetLength / prevMaxSpeed; - - if (! this.m_ignoreCost) { - int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; - if (ticketCost != 0) { - comparisonValue += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; - } - } - - if (nextLaneIndex < numLanes) { - NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; - BufferItem nextItem; + // check if pedestrians are not allowed to cross here + if (!junctionManager.IsPedestrianCrossingAllowed(nextSegmentId, nextIsStartNode)) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- pedestrian crossing not allowed!\n"); + FlushCostLog(logBuf); + } +#endif + return; + } + + // check if pedestrian light won't change to green + ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(nextSegmentId, nextIsStartNode, false); + if (lights != null) { + if (lights.InvalidPedestrianLight) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- invalid pedestrian lights!\n"); + FlushCostLog(logBuf); + } +#endif + return; + } + } + } + // NON-STOCK CODE END + + // NON-STOCK CODE START + /*if (!_allowEscapeTransport) { + ushort transportLineId = netManager.m_nodes.m_buffer[targetNodeId].m_transportLine; + if (transportLineId != 0 && Singleton.instance.m_lines.m_buffer[transportLineId].Info.m_transportType == TransportInfo.TransportType.EvacuationBus) + return; + }*/ + // NON-STOCK CODE END + NetInfo nextSegmentInfo = nextSegment.Info; + NetInfo prevSegmentInfo = prevSegment.Info; + int numLanes = nextSegmentInfo.m_lanes.Length; + float distance; + byte offset; + Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + if (nextSegmentId == item.m_position.m_segment) { + // next segment is previous segment + Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + distance = Vector3.Distance(a, b); + offset = connectOffset; + } else { + // next segment differs from previous segment + NetInfo.Direction direction = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; + Vector3 a; + if ((byte)(direction & NetInfo.Direction.Forward) != 0) { + a = netManager.m_lanes.m_buffer[nextLaneId].m_bezier.d; + } else { + a = netManager.m_lanes.m_buffer[nextLaneId].m_bezier.a; + } + distance = Vector3.Distance(a, b); + offset = (byte)(((direction & NetInfo.Direction.Forward) == 0) ? 0 : 255); + } + float prevMaxSpeed = 1f; + float prevSpeed = 1f; + NetInfo.LaneType laneType = NetInfo.LaneType.None; + VehicleInfo.VehicleType vehicleType = VehicleInfo.VehicleType.None; // NON-STOCK CODE + if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { + NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; + prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // SpeedLimitManager.GetLockFreeGameSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, ref lane2); // NON-STOCK CODE + laneType = prevLaneInfo.m_laneType; + vehicleType = prevLaneInfo.m_vehicleType; // NON-STOCK CODE + if ((byte)(laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { + laneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + prevSpeed = this.CalculateLaneSpeed(prevMaxSpeed, laneSwitchOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE + } + + float segLength; + if (laneType == NetInfo.LaneType.PublicTransport) { + segLength = netManager.m_lanes.m_buffer[item.m_laneID].m_length; + } else { + segLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); + } + float offsetLength = (float)Mathf.Abs((int)(laneSwitchOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * segLength; + float methodDistance = item.m_methodDistance + offsetLength; + float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * this.m_maxLength); + float duration = item.m_duration + offsetLength / prevMaxSpeed; + + if (! this.m_ignoreCost) { + int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; + if (ticketCost != 0) { + comparisonValue += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; + } + } + + if (nextLaneIndex < numLanes) { + NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; + BufferItem nextItem; #if COUNTSEGMENTSTONEXTJUNCTION // NON-STOCK CODE START // if (prevIsRealJunction) { @@ -2509,315 +2514,315 @@ private void ProcessItemPedBicycle(bool debug, BufferItem item, ushort nextNodeI } // NON-STOCK CODE END // #endif - nextItem.m_position.m_segment = nextSegmentId; - nextItem.m_position.m_lane = (byte)nextLaneIndex; - nextItem.m_position.m_offset = offset; - if ((nextLaneInfo.m_laneType & laneType) == NetInfo.LaneType.None) { - nextItem.m_methodDistance = 0f; - } else { - if (item.m_methodDistance == 0f) { - comparisonValue += 100f / (0.25f * this.m_maxLength); - } - nextItem.m_methodDistance = methodDistance + distance; - } - - float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); // NON-STOCK CODE - if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { - nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.25f * this.m_maxLength); - nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); - if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { - nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); - } else { - nextItem.m_direction = nextLaneInfo.m_finalDirection; - } - if (nextLaneId == this.m_startLaneA) { - if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetA) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetA)) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- start lane A reached in wrong direction!\n"); - FlushCostLog(logBuf); - } -#endif - return; - } - float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE - float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetA)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; - } - if (nextLaneId == this.m_startLaneB) { - if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetB) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetB)) { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- start lane B reached in wrong direction!\n"); - FlushCostLog(logBuf); - } -#endif - return; - } - float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE - float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; - } - nextItem.m_laneID = nextLaneId; - nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); - nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); - nextItem.m_trafficRand = 0; -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: *ADDING*\n" + - "\t" + $"nextItem.m_laneID={nextItem.m_laneID}\n" + - "\t" + $"nextItem.m_lanesUsed={nextItem.m_lanesUsed}\n" + - "\t" + $"nextItem.m_vehiclesUsed={nextItem.m_vehiclesUsed }\n" + - "\t" + $"nextItem.m_comparisonValue={nextItem.m_comparisonValue}\n" + - "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}\n" - ); - FlushCostLog(logBuf); - - m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); - } -#endif - this.AddBufferItem(nextItem, item.m_position); - } else { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- lane incompatible or method distance too large!\n" + - "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}\n" + - "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}\n" - ); - FlushCostLog(logBuf); - } -#endif - } - } else { -#if DEBUGNEWPF - if (debug) { - logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- nextLaneIndex >= numLanes ({nextLaneIndex} >= {numLanes})!\n"); - FlushCostLog(logBuf); - } -#endif - } - } - - private float CalculateLaneSpeed(float speedLimit, byte startOffset, byte endOffset, ref NetSegment segment, NetInfo.Lane laneInfo) { - /*if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram)) != VehicleInfo.VehicleType.None) - speedLimit = laneInfo.m_speedLimit; - */ - NetInfo.Direction direction = ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); - if ((byte)(direction & NetInfo.Direction.Avoid) == 0) { - return speedLimit; - } - if (endOffset > startOffset && direction == NetInfo.Direction.AvoidForward) { - return speedLimit * 0.1f; - } - if (endOffset < startOffset && direction == NetInfo.Direction.AvoidBackward) { - return speedLimit * 0.1f; - } - return speedLimit * 0.2f; - } - - private void AddBufferItem(BufferItem item, PathUnit.Position target) { - uint laneLocation = m_laneLocation[item.m_laneID]; - uint locPathFindIndex = laneLocation >> 16; // upper 16 bit, expected (?) path find index - int bufferIndex = (int)(laneLocation & 65535u); // lower 16 bit - int comparisonBufferPos; - if (locPathFindIndex == m_pathFindIndex) { - if (item.m_comparisonValue >= m_buffer[bufferIndex].m_comparisonValue) { - return; - } - - int bufferPosIndex = bufferIndex >> 6; // arithmetic shift (sign stays), upper 10 bit - int bufferPos = bufferIndex & -64; // upper 10 bit (no shift) - if (bufferPosIndex < m_bufferMinPos || (bufferPosIndex == m_bufferMinPos && bufferPos < m_bufferMin[bufferPosIndex])) { - return; - } - - comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); - if (comparisonBufferPos == bufferPosIndex) { - m_buffer[bufferIndex] = item; - m_laneTarget[item.m_laneID] = target; - return; - } - - int newBufferIndex = bufferPosIndex << 6 | m_bufferMax[bufferPosIndex]--; - BufferItem bufferItem = m_buffer[newBufferIndex]; - m_laneLocation[bufferItem.m_laneID] = laneLocation; - m_buffer[bufferIndex] = bufferItem; - } else { - comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); - } - - if (comparisonBufferPos >= 1024) { - return; - } - - if (comparisonBufferPos < 0) { - return; - } - - while (m_bufferMax[comparisonBufferPos] == 63) { - ++comparisonBufferPos; - if (comparisonBufferPos == 1024) { - return; - } - } - - if (comparisonBufferPos > m_bufferMaxPos) { - m_bufferMaxPos = comparisonBufferPos; - } - - bufferIndex = (comparisonBufferPos << 6 | ++m_bufferMax[comparisonBufferPos]); - m_buffer[bufferIndex] = item; - m_laneLocation[item.m_laneID] = (m_pathFindIndex << 16 | (uint)bufferIndex); - m_laneTarget[item.m_laneID] = target; - } - - private void GetLaneDirection(PathUnit.Position pathPos, out NetInfo.Direction direction, out NetInfo.LaneType laneType, out VehicleInfo.VehicleType vehicleType) { - NetManager instance = Singleton.instance; - NetInfo info = instance.m_segments.m_buffer[pathPos.m_segment].Info; - if (info.m_lanes.Length > pathPos.m_lane) { - direction = info.m_lanes[pathPos.m_lane].m_finalDirection; - laneType = info.m_lanes[pathPos.m_lane].m_laneType; - vehicleType = info.m_lanes[pathPos.m_lane].m_vehicleType; - if ((instance.m_segments.m_buffer[pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { - direction = NetInfo.InvertDirection(direction); - } - } else { - direction = NetInfo.Direction.None; - laneType = NetInfo.LaneType.None; - vehicleType = VehicleInfo.VehicleType.None; - } - } - - private void PathFindThread() { - while (true) { - //Log.Message($"Pathfind Thread #{Thread.CurrentThread.ManagedThreadId} iteration!"); - try { - Monitor.Enter(QueueLock); - - while (QueueFirst == 0u && !Terminated) { - Monitor.Wait(QueueLock); - } - - if (Terminated) { - break; - } - Calculating = QueueFirst; - QueueFirst = CustomPathManager._instance.queueItems[Calculating].nextPathUnitId; - //QueueFirst = PathUnits.m_buffer[Calculating].m_nextPathUnit; - if (QueueFirst == 0u) { - QueueLast = 0u; - m_queuedPathFindCount = 0; - } else { - --m_queuedPathFindCount; - } - - CustomPathManager._instance.queueItems[Calculating].nextPathUnitId = 0u; - //PathUnits.m_buffer[Calculating].m_nextPathUnit = 0u; - - // check if path unit is created - /*if ((PathUnits.m_buffer[Calculating].m_pathFindFlags & PathUnit.FLAG_CREATED) == 0) { - Log.Warning($"CustomPathFind: Refusing to calculate path unit {Calculating} which is not created!"); - continue; - }*/ - - PathUnits.m_buffer[Calculating].m_pathFindFlags = (byte)((PathUnits.m_buffer[Calculating].m_pathFindFlags & ~PathUnit.FLAG_CREATED) | PathUnit.FLAG_CALCULATING); - this.queueItem = CustomPathManager._instance.queueItems[Calculating]; - } catch (Exception e) { - Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (1): {e.ToString()}"); - } finally { - Monitor.Exit(QueueLock); - } - - // calculate path unit - try { - PathFindImplementation(Calculating, ref PathUnits.m_buffer[Calculating]); - } catch (Exception ex) { - Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (2): {ex.ToString()}"); - //UIView.ForwardException(ex); + nextItem.m_position.m_segment = nextSegmentId; + nextItem.m_position.m_lane = (byte)nextLaneIndex; + nextItem.m_position.m_offset = offset; + if ((nextLaneInfo.m_laneType & laneType) == NetInfo.LaneType.None) { + nextItem.m_methodDistance = 0f; + } else { + if (item.m_methodDistance == 0f) { + comparisonValue += 100f / (0.25f * this.m_maxLength); + } + nextItem.m_methodDistance = methodDistance + distance; + } + + float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); // NON-STOCK CODE + if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { + nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.25f * this.m_maxLength); + nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); + if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { + nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); + } else { + nextItem.m_direction = nextLaneInfo.m_finalDirection; + } + if (nextLaneId == this.m_startLaneA) { + if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetA) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetA)) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- start lane A reached in wrong direction!\n"); + FlushCostLog(logBuf); + } +#endif + return; + } + float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE + float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetA)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; + } + if (nextLaneId == this.m_startLaneB) { + if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetB) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetB)) { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- start lane B reached in wrong direction!\n"); + FlushCostLog(logBuf); + } +#endif + return; + } + float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE + float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; + } + nextItem.m_laneID = nextLaneId; + nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); + nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); + nextItem.m_trafficRand = 0; +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: *ADDING*\n" + + "\t" + $"nextItem.m_laneID={nextItem.m_laneID}\n" + + "\t" + $"nextItem.m_lanesUsed={nextItem.m_lanesUsed}\n" + + "\t" + $"nextItem.m_vehiclesUsed={nextItem.m_vehiclesUsed }\n" + + "\t" + $"nextItem.m_comparisonValue={nextItem.m_comparisonValue}\n" + + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}\n" + ); + FlushCostLog(logBuf); + + m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); + } +#endif + this.AddBufferItem(nextItem, item.m_position); + } else { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- lane incompatible or method distance too large!\n" + + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}\n" + + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}\n" + ); + FlushCostLog(logBuf); + } +#endif + } + } else { +#if DEBUGNEWPF + if (debug) { + logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- nextLaneIndex >= numLanes ({nextLaneIndex} >= {numLanes})!\n"); + FlushCostLog(logBuf); + } +#endif + } + } + + private float CalculateLaneSpeed(float speedLimit, byte startOffset, byte endOffset, ref NetSegment segment, NetInfo.Lane laneInfo) { + /*if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram)) != VehicleInfo.VehicleType.None) + speedLimit = laneInfo.m_speedLimit; + */ + NetInfo.Direction direction = ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); + if ((byte)(direction & NetInfo.Direction.Avoid) == 0) { + return speedLimit; + } + if (endOffset > startOffset && direction == NetInfo.Direction.AvoidForward) { + return speedLimit * 0.1f; + } + if (endOffset < startOffset && direction == NetInfo.Direction.AvoidBackward) { + return speedLimit * 0.1f; + } + return speedLimit * 0.2f; + } + + private void AddBufferItem(BufferItem item, PathUnit.Position target) { + uint laneLocation = m_laneLocation[item.m_laneID]; + uint locPathFindIndex = laneLocation >> 16; // upper 16 bit, expected (?) path find index + int bufferIndex = (int)(laneLocation & 65535u); // lower 16 bit + int comparisonBufferPos; + if (locPathFindIndex == m_pathFindIndex) { + if (item.m_comparisonValue >= m_buffer[bufferIndex].m_comparisonValue) { + return; + } + + int bufferPosIndex = bufferIndex >> 6; // arithmetic shift (sign stays), upper 10 bit + int bufferPos = bufferIndex & -64; // upper 10 bit (no shift) + if (bufferPosIndex < m_bufferMinPos || (bufferPosIndex == m_bufferMinPos && bufferPos < m_bufferMin[bufferPosIndex])) { + return; + } + + comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); + if (comparisonBufferPos == bufferPosIndex) { + m_buffer[bufferIndex] = item; + m_laneTarget[item.m_laneID] = target; + return; + } + + int newBufferIndex = bufferPosIndex << 6 | m_bufferMax[bufferPosIndex]--; + BufferItem bufferItem = m_buffer[newBufferIndex]; + m_laneLocation[bufferItem.m_laneID] = laneLocation; + m_buffer[bufferIndex] = bufferItem; + } else { + comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); + } + + if (comparisonBufferPos >= 1024) { + return; + } + + if (comparisonBufferPos < 0) { + return; + } + + while (m_bufferMax[comparisonBufferPos] == 63) { + ++comparisonBufferPos; + if (comparisonBufferPos == 1024) { + return; + } + } + + if (comparisonBufferPos > m_bufferMaxPos) { + m_bufferMaxPos = comparisonBufferPos; + } + + bufferIndex = (comparisonBufferPos << 6 | ++m_bufferMax[comparisonBufferPos]); + m_buffer[bufferIndex] = item; + m_laneLocation[item.m_laneID] = (m_pathFindIndex << 16 | (uint)bufferIndex); + m_laneTarget[item.m_laneID] = target; + } + + private void GetLaneDirection(PathUnit.Position pathPos, out NetInfo.Direction direction, out NetInfo.LaneType laneType, out VehicleInfo.VehicleType vehicleType) { + NetManager instance = Singleton.instance; + NetInfo info = instance.m_segments.m_buffer[pathPos.m_segment].Info; + if (info.m_lanes.Length > pathPos.m_lane) { + direction = info.m_lanes[pathPos.m_lane].m_finalDirection; + laneType = info.m_lanes[pathPos.m_lane].m_laneType; + vehicleType = info.m_lanes[pathPos.m_lane].m_vehicleType; + if ((instance.m_segments.m_buffer[pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { + direction = NetInfo.InvertDirection(direction); + } + } else { + direction = NetInfo.Direction.None; + laneType = NetInfo.LaneType.None; + vehicleType = VehicleInfo.VehicleType.None; + } + } + + private void PathFindThread() { + while (true) { + //Log.Message($"Pathfind Thread #{Thread.CurrentThread.ManagedThreadId} iteration!"); + try { + Monitor.Enter(QueueLock); + + while (QueueFirst == 0u && !Terminated) { + Monitor.Wait(QueueLock); + } + + if (Terminated) { + break; + } + Calculating = QueueFirst; + QueueFirst = CustomPathManager._instance.queueItems[Calculating].nextPathUnitId; + //QueueFirst = PathUnits.m_buffer[Calculating].m_nextPathUnit; + if (QueueFirst == 0u) { + QueueLast = 0u; + m_queuedPathFindCount = 0; + } else { + --m_queuedPathFindCount; + } + + CustomPathManager._instance.queueItems[Calculating].nextPathUnitId = 0u; + //PathUnits.m_buffer[Calculating].m_nextPathUnit = 0u; + + // check if path unit is created + /*if ((PathUnits.m_buffer[Calculating].m_pathFindFlags & PathUnit.FLAG_CREATED) == 0) { + Log.Warning($"CustomPathFind: Refusing to calculate path unit {Calculating} which is not created!"); + continue; + }*/ + + PathUnits.m_buffer[Calculating].m_pathFindFlags = (byte)((PathUnits.m_buffer[Calculating].m_pathFindFlags & ~PathUnit.FLAG_CREATED) | PathUnit.FLAG_CALCULATING); + this.queueItem = CustomPathManager._instance.queueItems[Calculating]; + } catch (Exception e) { + Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (1): {e.ToString()}"); + } finally { + Monitor.Exit(QueueLock); + } + + // calculate path unit + try { + PathFindImplementation(Calculating, ref PathUnits.m_buffer[Calculating]); + } catch (Exception ex) { + Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (2): {ex.ToString()}"); + //UIView.ForwardException(ex); #if DEBUG - ++m_failedPathFinds; + ++m_failedPathFinds; #if DEBUGNEWPF - bool debug = this.m_debug; - if (debug) - Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {Calculating} -- exception occurred in PathFindImplementation"); + bool debug = this.m_debug; + if (debug) + Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {Calculating} -- exception occurred in PathFindImplementation"); #endif #endif - //CustomPathManager._instance.ResetQueueItem(Calculating); + //CustomPathManager._instance.ResetQueueItem(Calculating); - PathUnits.m_buffer[Calculating].m_pathFindFlags |= PathUnit.FLAG_FAILED; - } finally { - } - //tCurrentState = 10; + PathUnits.m_buffer[Calculating].m_pathFindFlags |= PathUnit.FLAG_FAILED; + } finally { + } + //tCurrentState = 10; #if DEBUGLOCKS lockIter = 0; #endif - try { - Monitor.Enter(QueueLock); - - PathUnits.m_buffer[Calculating].m_pathFindFlags = (byte)(PathUnits.m_buffer[Calculating].m_pathFindFlags & ~PathUnit.FLAG_CALCULATING); - - // NON-STOCK CODE START - try { - Monitor.Enter(_bufferLock); - CustomPathManager._instance.queueItems[Calculating].queued = false; - CustomPathManager._instance.ReleasePath(Calculating); - } finally { - Monitor.Exit(this._bufferLock); - } - // NON-STOCK CODE END - - Calculating = 0u; - Monitor.Pulse(QueueLock); - } catch (Exception e) { - Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (3): {e.ToString()}"); - } finally { - Monitor.Exit(QueueLock); - } - } - } - - /// - /// Determines if a given lane may be used by the vehicle whose path is currently being calculated. - /// - /// - /// - /// - /// - /// - /// - protected virtual bool CanUseLane(bool debug, ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo) { - if (!Options.vehicleRestrictionsEnabled) - return true; - - if (queueItem.vehicleType == ExtVehicleType.None || queueItem.vehicleType == ExtVehicleType.Tram) - return true; - - /*if (laneInfo == null) - laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex];*/ - - if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) - return true; - - ExtVehicleType allowedTypes = vehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - - return ((allowedTypes & queueItem.vehicleType) != ExtVehicleType.None); - } - - /// - /// Determines the speed limit for the given lane. - /// - /// - /// - /// - /// - /// - protected virtual float GetLaneSpeedLimit(ushort segmentId, byte laneIndex, uint laneId, NetInfo.Lane lane) { - return Options.customSpeedLimitsEnabled ? speedLimitManager.GetLockFreeGameSpeedLimit(segmentId, laneIndex, laneId, lane) : lane.m_speedLimit; - } - } -} + try { + Monitor.Enter(QueueLock); + + PathUnits.m_buffer[Calculating].m_pathFindFlags = (byte)(PathUnits.m_buffer[Calculating].m_pathFindFlags & ~PathUnit.FLAG_CALCULATING); + + // NON-STOCK CODE START + try { + Monitor.Enter(_bufferLock); + CustomPathManager._instance.queueItems[Calculating].queued = false; + CustomPathManager._instance.ReleasePath(Calculating); + } finally { + Monitor.Exit(this._bufferLock); + } + // NON-STOCK CODE END + + Calculating = 0u; + Monitor.Pulse(QueueLock); + } catch (Exception e) { + Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (3): {e.ToString()}"); + } finally { + Monitor.Exit(QueueLock); + } + } + } + + /// + /// Determines if a given lane may be used by the vehicle whose path is currently being calculated. + /// + /// + /// + /// + /// + /// + /// + protected virtual bool CanUseLane(bool debug, ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo) { + if (!Options.vehicleRestrictionsEnabled) + return true; + + if (queueItem.vehicleType == ExtVehicleType.None || queueItem.vehicleType == ExtVehicleType.Tram) + return true; + + /*if (laneInfo == null) + laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex];*/ + + if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) + return true; + + ExtVehicleType allowedTypes = vehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); + + return ((allowedTypes & queueItem.vehicleType) != ExtVehicleType.None); + } + + /// + /// Determines the speed limit for the given lane. + /// + /// + /// + /// + /// + /// + protected virtual float GetLaneSpeedLimit(ushort segmentId, byte laneIndex, uint laneId, NetInfo.Lane lane) { + return Options.customSpeedLimitsEnabled ? speedLimitManager.GetLockFreeGameSpeedLimit(segmentId, laneIndex, laneId, lane) : lane.m_speedLimit; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs b/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs index ca303ffd2..2b6d75223 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs @@ -1,3308 +1,3310 @@ -using ColossalFramework; -using ColossalFramework.Math; -using ColossalFramework.UI; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.RedirectionFramework.Attributes; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using TrafficManager.TrafficLight; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; - -namespace TrafficManager.Custom.PathFinding { +namespace TrafficManager.Custom.PathFinding { + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Threading; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using ColossalFramework.Math; + using ColossalFramework.UI; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + #if PF2 - [TargetType(typeof(PathFind))] + [TargetType(typeof(PathFind))] #endif - public class CustomPathFind2 : PathFind { - private const float BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR = 0.003921569f; - private const float TICKET_COST_CONVERSION_FACTOR = BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; + public class CustomPathFind2 : PathFind { + private const float BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR = 0.003921569f; + private const float TICKET_COST_CONVERSION_FACTOR = BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; #if ROUTING - private readonly RoutingManager m_routingManager = RoutingManager.Instance; + private readonly RoutingManager m_routingManager = RoutingManager.Instance; #endif #if JUNCTIONRESTRICTIONS - private readonly JunctionRestrictionsManager m_junctionManager = JunctionRestrictionsManager.Instance; + private readonly JunctionRestrictionsManager m_junctionManager = JunctionRestrictionsManager.Instance; #endif #if VEHICLERESTRICTIONS - private readonly VehicleRestrictionsManager m_vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; + private readonly VehicleRestrictionsManager m_vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; #endif #if SPEEDLIMITS - private readonly SpeedLimitManager m_speedLimitManager = SpeedLimitManager.Instance; + private readonly SpeedLimitManager m_speedLimitManager = SpeedLimitManager.Instance; #endif #if CUSTOMTRAFFICLIGHTS - private readonly CustomSegmentLightsManager m_customTrafficLightsManager = CustomSegmentLightsManager.Instance; + private readonly CustomSegmentLightsManager m_customTrafficLightsManager = CustomSegmentLightsManager.Instance; #endif #if ADVANCEDAI && ROUTING - private readonly TrafficMeasurementManager m_trafficMeasurementManager = TrafficMeasurementManager.Instance; -#endif - private GlobalConfig m_conf = null; - - private struct BufferItem { - public PathUnit.Position m_position; - public float m_comparisonValue; - public float m_methodDistance; - public float m_duration; - public uint m_laneID; - public NetInfo.Direction m_direction; - public NetInfo.LaneType m_lanesUsed; + private readonly TrafficMeasurementManager m_trafficMeasurementManager = TrafficMeasurementManager.Instance; +#endif + private GlobalConfig m_conf = null; + + private struct BufferItem { + public PathUnit.Position m_position; + public float m_comparisonValue; + public float m_methodDistance; + public float m_duration; + public uint m_laneID; + public NetInfo.Direction m_direction; + public NetInfo.LaneType m_lanesUsed; #if PARKINGAI - public VehicleInfo.VehicleType m_vehiclesUsed; + public VehicleInfo.VehicleType m_vehiclesUsed; #endif #if ADVANCEDAI && ROUTING - public float m_trafficRand; -#endif - public override string ToString() { - return $"[BufferItem\n" + - "\t" + $"m_position=(s#({m_position.m_segment}), l#({m_position.m_lane}), o#({m_position.m_offset}))\n" + - "\t" + $"m_laneID={m_laneID}\n" + - "\t" + $"m_comparisonValue={m_comparisonValue}\n" + - "\t" + $"m_methodDistance={m_methodDistance}\n" + - "\t" + $"m_duration={m_duration}\n" + - "\t" + $"m_direction={m_direction}\n" + - "\t" + $"m_lanesUsed={m_lanesUsed}\n" + + public float m_trafficRand; +#endif + public override string ToString() { + return $"[BufferItem\n" + + "\t" + $"m_position=(s#({m_position.m_segment}), l#({m_position.m_lane}), o#({m_position.m_offset}))\n" + + "\t" + $"m_laneID={m_laneID}\n" + + "\t" + $"m_comparisonValue={m_comparisonValue}\n" + + "\t" + $"m_methodDistance={m_methodDistance}\n" + + "\t" + $"m_duration={m_duration}\n" + + "\t" + $"m_direction={m_direction}\n" + + "\t" + $"m_lanesUsed={m_lanesUsed}\n" + #if PARKINGAI - "\t" + $"m_vehiclesUsed={m_vehiclesUsed}\n" + + "\t" + $"m_vehiclesUsed={m_vehiclesUsed}\n" + #endif #if ADVANCEDAI && ROUTING - "\t" + $"m_trafficRand={m_trafficRand}\n" + -#endif - "BufferItem]"; - } - } - - private enum LaneChangingCostCalculationMode { - None, - ByLaneDistance, - ByGivenDistance - } - - // private stock fields - FieldInfo pathUnitsField; - FieldInfo queueFirstField; - FieldInfo queueLastField; - FieldInfo queueLockField; - FieldInfo calculatingField; - FieldInfo terminatedField; - FieldInfo pathFindThreadField; - - private Array32 m_pathUnits { - get { return pathUnitsField.GetValue(this) as Array32; } - set { pathUnitsField.SetValue(this, value); } - } - - private uint m_queueFirst { - get { return (uint)queueFirstField.GetValue(this); } - set { queueFirstField.SetValue(this, value); } - } - - private uint m_queueLast { - get { return (uint)queueLastField.GetValue(this); } - set { queueLastField.SetValue(this, value); } - } - - private uint m_calculating { - get { return (uint)calculatingField.GetValue(this); } - set { calculatingField.SetValue(this, value); } - } - - private object m_queueLock { - get { return queueLockField.GetValue(this); } - set { queueLockField.SetValue(this, value); } - } - - private Thread m_customPathFindThread { - get { return (Thread)pathFindThreadField.GetValue(this); } - set { pathFindThreadField.SetValue(this, value); } - } - - private bool m_terminated { - get { return (bool)terminatedField.GetValue(this); } - set { terminatedField.SetValue(this, value); } - } - - // stock fields - public ThreadProfiler m_pathfindProfiler; - private object m_bufferLock; - private int m_bufferMinPos; - private int m_bufferMaxPos; - private uint[] m_laneLocation; - private PathUnit.Position[] m_laneTarget; - private BufferItem[] m_buffer; - private int[] m_bufferMin; - private int[] m_bufferMax; - private float m_maxLength; - private uint m_startLaneA; - private uint m_startLaneB; - private uint m_endLaneA; - private uint m_endLaneB; - private uint m_vehicleLane; - private byte m_startOffsetA; - private byte m_startOffsetB; - private byte m_vehicleOffset; - private NetSegment.Flags m_carBanMask; - private bool m_ignoreBlocked; - private bool m_stablePath; - private bool m_randomParking; - private bool m_transportVehicle; - private bool m_ignoreCost; - private NetSegment.Flags m_disableMask; - private Randomizer m_pathRandomizer; - private uint m_pathFindIndex; - private NetInfo.LaneType m_laneTypes; - private VehicleInfo.VehicleType m_vehicleTypes; - - // custom fields - private PathUnitQueueItem m_queueItem; - private bool m_isHeavyVehicle; -#if DEBUG - public uint m_failedPathFinds = 0; - public uint m_succeededPathFinds = 0; - private bool m_debug = false; - private IDictionary> m_debugPositions = null; + "\t" + $"m_trafficRand={m_trafficRand}\n" + +#endif + "BufferItem]"; + } + } + + private enum LaneChangingCostCalculationMode { + None, + ByLaneDistance, + ByGivenDistance + } + + // private stock fields + FieldInfo pathUnitsField; + FieldInfo queueFirstField; + FieldInfo queueLastField; + FieldInfo queueLockField; + FieldInfo calculatingField; + FieldInfo terminatedField; + FieldInfo pathFindThreadField; + + private Array32 m_pathUnits { + get { return pathUnitsField.GetValue(this) as Array32; } + set { pathUnitsField.SetValue(this, value); } + } + + private uint m_queueFirst { + get { return (uint)queueFirstField.GetValue(this); } + set { queueFirstField.SetValue(this, value); } + } + + private uint m_queueLast { + get { return (uint)queueLastField.GetValue(this); } + set { queueLastField.SetValue(this, value); } + } + + private uint m_calculating { + get { return (uint)calculatingField.GetValue(this); } + set { calculatingField.SetValue(this, value); } + } + + private object m_queueLock { + get { return queueLockField.GetValue(this); } + set { queueLockField.SetValue(this, value); } + } + + private Thread m_customPathFindThread { + get { return (Thread)pathFindThreadField.GetValue(this); } + set { pathFindThreadField.SetValue(this, value); } + } + + private bool m_terminated { + get { return (bool)terminatedField.GetValue(this); } + set { terminatedField.SetValue(this, value); } + } + + // stock fields + public ThreadProfiler m_pathfindProfiler; + private object m_bufferLock; + private int m_bufferMinPos; + private int m_bufferMaxPos; + private uint[] m_laneLocation; + private PathUnit.Position[] m_laneTarget; + private BufferItem[] m_buffer; + private int[] m_bufferMin; + private int[] m_bufferMax; + private float m_maxLength; + private uint m_startLaneA; + private uint m_startLaneB; + private uint m_endLaneA; + private uint m_endLaneB; + private uint m_vehicleLane; + private byte m_startOffsetA; + private byte m_startOffsetB; + private byte m_vehicleOffset; + private NetSegment.Flags m_carBanMask; + private bool m_ignoreBlocked; + private bool m_stablePath; + private bool m_randomParking; + private bool m_transportVehicle; + private bool m_ignoreCost; + private NetSegment.Flags m_disableMask; + private Randomizer m_pathRandomizer; + private uint m_pathFindIndex; + private NetInfo.LaneType m_laneTypes; + private VehicleInfo.VehicleType m_vehicleTypes; + + // custom fields + private PathUnitQueueItem m_queueItem; + private bool m_isHeavyVehicle; +#if DEBUG + public uint m_failedPathFinds = 0; + public uint m_succeededPathFinds = 0; + private bool m_debug = false; + private IDictionary> m_debugPositions = null; #endif #if PARKINGAI || JUNCTIONRESTRICTIONS - private ushort m_startSegmentA; - private ushort m_startSegmentB; + private ushort m_startSegmentA; + private ushort m_startSegmentB; #endif #if ROUTING - private bool m_isRoadVehicle; - private bool m_isLaneArrowObeyingEntity; - //private bool m_isLaneConnectionObeyingEntity; -#endif - - private void Awake() { - Type stockPathFindType = typeof(PathFind); - const BindingFlags fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; - - pathUnitsField = stockPathFindType.GetField("m_pathUnits", fieldFlags); - queueFirstField = stockPathFindType.GetField("m_queueFirst", fieldFlags); - queueLastField = stockPathFindType.GetField("m_queueLast", fieldFlags); - queueLockField = stockPathFindType.GetField("m_queueLock", fieldFlags); - terminatedField = stockPathFindType.GetField("m_terminated", fieldFlags); - calculatingField = stockPathFindType.GetField("m_calculating", fieldFlags); - pathFindThreadField = stockPathFindType.GetField("m_pathFindThread", fieldFlags); - - m_pathfindProfiler = new ThreadProfiler(); - m_laneLocation = new uint[262144]; - m_laneTarget = new PathUnit.Position[262144]; - m_buffer = new BufferItem[65536]; - m_bufferMin = new int[1024]; - m_bufferMax = new int[1024]; - m_queueLock = new object(); - m_bufferLock = Singleton.instance.m_bufferLock; - m_pathUnits = Singleton.instance.m_pathUnits; - m_customPathFindThread = new Thread(PathFindThread); - m_customPathFindThread.Name = "Pathfind"; - m_customPathFindThread.Priority = SimulationManager.SIMULATION_PRIORITY; - m_customPathFindThread.Start(); - - if (!m_customPathFindThread.IsAlive) { - CODebugBase.Error(LogChannel.Core, "Path find thread failed to start!"); - } - } - - private void OnDestroy() { - try { - Monitor.Enter(m_queueLock); - m_terminated = true; - Monitor.PulseAll(m_queueLock); - } finally { - Monitor.Exit(m_queueLock); - } - } + private bool m_isRoadVehicle; + private bool m_isLaneArrowObeyingEntity; + //private bool m_isLaneConnectionObeyingEntity; +#endif + + private void Awake() { + Type stockPathFindType = typeof(PathFind); + const BindingFlags fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; + + pathUnitsField = stockPathFindType.GetField("m_pathUnits", fieldFlags); + queueFirstField = stockPathFindType.GetField("m_queueFirst", fieldFlags); + queueLastField = stockPathFindType.GetField("m_queueLast", fieldFlags); + queueLockField = stockPathFindType.GetField("m_queueLock", fieldFlags); + terminatedField = stockPathFindType.GetField("m_terminated", fieldFlags); + calculatingField = stockPathFindType.GetField("m_calculating", fieldFlags); + pathFindThreadField = stockPathFindType.GetField("m_pathFindThread", fieldFlags); + + m_pathfindProfiler = new ThreadProfiler(); + m_laneLocation = new uint[262144]; + m_laneTarget = new PathUnit.Position[262144]; + m_buffer = new BufferItem[65536]; + m_bufferMin = new int[1024]; + m_bufferMax = new int[1024]; + m_queueLock = new object(); + m_bufferLock = Singleton.instance.m_bufferLock; + m_pathUnits = Singleton.instance.m_pathUnits; + m_customPathFindThread = new Thread(PathFindThread); + m_customPathFindThread.Name = "Pathfind"; + m_customPathFindThread.Priority = SimulationManager.SIMULATION_PRIORITY; + m_customPathFindThread.Start(); + + if (!m_customPathFindThread.IsAlive) { + CODebugBase.Error(LogChannel.Core, "Path find thread failed to start!"); + } + } + + private void OnDestroy() { + try { + Monitor.Enter(m_queueLock); + m_terminated = true; + Monitor.PulseAll(m_queueLock); + } finally { + Monitor.Exit(m_queueLock); + } + } #if PF2 - [RedirectMethod] -#endif - public new bool CalculatePath(uint unit, bool skipQueue) { - return ExtCalculatePath(unit, skipQueue); - } - - public bool ExtCalculatePath(uint unit, bool skipQueue) { - if (CustomPathManager._instance.AddPathReference(unit)) { - try { - Monitor.Enter(m_queueLock); - - if (skipQueue) { - if (m_queueLast == 0) { - m_queueLast = unit; - } else { - // NON-STOCK CODE START - CustomPathManager._instance.queueItems[unit].nextPathUnitId = m_queueFirst; - // NON-STOCK CODE END - // PathUnits.m_buffer[unit].m_nextPathUnit = QueueFirst; // stock code commented - } - m_queueFirst = unit; - } else { - if (m_queueLast == 0) { - m_queueFirst = unit; - } else { - // NON-STOCK CODE START - CustomPathManager._instance.queueItems[m_queueLast].nextPathUnitId = unit; - // NON-STOCK CODE END - // PathUnits.m_buffer[QueueLast].m_nextPathUnit = unit; // stock code commented - } - m_queueLast = unit; - } - - m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_CREATED; - m_queuedPathFindCount++; - - Monitor.Pulse(m_queueLock); - } finally { - Monitor.Exit(m_queueLock); - } - return true; - } - return false; - } - - private void PathFindImplementation(uint unit, ref PathUnit data) { - m_conf = GlobalConfig.Instance; // NON-STOCK CODE - - NetManager netManager = Singleton.instance; - - m_laneTypes = (NetInfo.LaneType)m_pathUnits.m_buffer[unit].m_laneTypes; - m_vehicleTypes = (VehicleInfo.VehicleType)m_pathUnits.m_buffer[unit].m_vehicleTypes; - m_maxLength = m_pathUnits.m_buffer[unit].m_length; - m_pathFindIndex = m_pathFindIndex + 1 & 0x7FFF; - m_pathRandomizer = new Randomizer(unit); - m_carBanMask = NetSegment.Flags.CarBan; - - m_isHeavyVehicle = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IS_HEAVY) != 0; // NON-STOCK CODE (refactored) - if (m_isHeavyVehicle) { - m_carBanMask |= NetSegment.Flags.HeavyBan; - } - - if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_READY) != 0) { - m_carBanMask |= NetSegment.Flags.WaitingPath; - } - - m_ignoreBlocked = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_BLOCKED) != 0; - m_stablePath = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_STABLE_PATH) != 0; - m_randomParking = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_RANDOM_PARKING) != 0; - m_transportVehicle = (m_laneTypes & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None; - m_ignoreCost = m_stablePath || (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_COST) != 0; - m_disableMask = NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed; - - if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_FLOODED) == 0) { - m_disableMask |= NetSegment.Flags.Flooded; - } - - if ((m_laneTypes & NetInfo.LaneType.Vehicle) != NetInfo.LaneType.None) { - m_laneTypes |= NetInfo.LaneType.TransportVehicle; - } + [RedirectMethod] +#endif + public new bool CalculatePath(uint unit, bool skipQueue) { + return ExtCalculatePath(unit, skipQueue); + } + + public bool ExtCalculatePath(uint unit, bool skipQueue) { + if (CustomPathManager._instance.AddPathReference(unit)) { + try { + Monitor.Enter(m_queueLock); + + if (skipQueue) { + if (m_queueLast == 0) { + m_queueLast = unit; + } else { + // NON-STOCK CODE START + CustomPathManager._instance.queueItems[unit].nextPathUnitId = m_queueFirst; + // NON-STOCK CODE END + // PathUnits.m_buffer[unit].m_nextPathUnit = QueueFirst; // stock code commented + } + m_queueFirst = unit; + } else { + if (m_queueLast == 0) { + m_queueFirst = unit; + } else { + // NON-STOCK CODE START + CustomPathManager._instance.queueItems[m_queueLast].nextPathUnitId = unit; + // NON-STOCK CODE END + // PathUnits.m_buffer[QueueLast].m_nextPathUnit = unit; // stock code commented + } + m_queueLast = unit; + } + + m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_CREATED; + m_queuedPathFindCount++; + + Monitor.Pulse(m_queueLock); + } finally { + Monitor.Exit(m_queueLock); + } + return true; + } + return false; + } + + private void PathFindImplementation(uint unit, ref PathUnit data) { + m_conf = GlobalConfig.Instance; // NON-STOCK CODE + + NetManager netManager = Singleton.instance; + + m_laneTypes = (NetInfo.LaneType)m_pathUnits.m_buffer[unit].m_laneTypes; + m_vehicleTypes = (VehicleInfo.VehicleType)m_pathUnits.m_buffer[unit].m_vehicleTypes; + m_maxLength = m_pathUnits.m_buffer[unit].m_length; + m_pathFindIndex = m_pathFindIndex + 1 & 0x7FFF; + m_pathRandomizer = new Randomizer(unit); + m_carBanMask = NetSegment.Flags.CarBan; + + m_isHeavyVehicle = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IS_HEAVY) != 0; // NON-STOCK CODE (refactored) + if (m_isHeavyVehicle) { + m_carBanMask |= NetSegment.Flags.HeavyBan; + } + + if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_READY) != 0) { + m_carBanMask |= NetSegment.Flags.WaitingPath; + } + + m_ignoreBlocked = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_BLOCKED) != 0; + m_stablePath = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_STABLE_PATH) != 0; + m_randomParking = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_RANDOM_PARKING) != 0; + m_transportVehicle = (m_laneTypes & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None; + m_ignoreCost = m_stablePath || (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_COST) != 0; + m_disableMask = NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed; + + if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_FLOODED) == 0) { + m_disableMask |= NetSegment.Flags.Flooded; + } + + if ((m_laneTypes & NetInfo.LaneType.Vehicle) != NetInfo.LaneType.None) { + m_laneTypes |= NetInfo.LaneType.TransportVehicle; + } #if ROUTING - m_isRoadVehicle = - (m_queueItem.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None; + m_isRoadVehicle = + (m_queueItem.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None; - m_isLaneArrowObeyingEntity = + m_isLaneArrowObeyingEntity = #if DEBUG - ! Options.allRelaxed && // debug option: all vehicle may ignore lane arrows + ! Options.allRelaxed && // debug option: all vehicle may ignore lane arrows #endif - (! Options.relaxedBusses || m_queueItem.vehicleType != ExtVehicleType.Bus) && // option: busses may ignore lane arrows - (m_vehicleTypes & LaneArrowManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None && - (m_queueItem.vehicleType & LaneArrowManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None - ; + (! Options.relaxedBusses || m_queueItem.vehicleType != ExtVehicleType.Bus) && // option: busses may ignore lane arrows + (m_vehicleTypes & LaneArrowManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None && + (m_queueItem.vehicleType & LaneArrowManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None + ; #endif #if DEBUG - m_debug = m_conf.Debug.Switches[0] && - (m_conf.Debug.ExtVehicleType == ExtVehicleType.None || m_queueItem.vehicleType == m_conf.Debug.ExtVehicleType) && - (m_conf.Debug.StartSegmentId == 0 || data.m_position00.m_segment == m_conf.Debug.StartSegmentId || data.m_position02.m_segment == m_conf.Debug.StartSegmentId) && - (m_conf.Debug.EndSegmentId == 0 || data.m_position01.m_segment == m_conf.Debug.EndSegmentId || data.m_position03.m_segment == m_conf.Debug.EndSegmentId) && - (m_conf.Debug.VehicleId == 0 || m_queueItem.vehicleId == m_conf.Debug.VehicleId) - ; - if (m_debug) { - m_debugPositions = new Dictionary>(); - } + m_debug = m_conf.Debug.Switches[0] + && (m_conf.Debug.ApiExtVehicleType == ExtVehicleType.None + || m_queueItem.vehicleType == m_conf.Debug.ApiExtVehicleType) + && (m_conf.Debug.StartSegmentId == 0 + || data.m_position00.m_segment == m_conf.Debug.StartSegmentId + || data.m_position02.m_segment == m_conf.Debug.StartSegmentId) + && (m_conf.Debug.EndSegmentId == 0 + || data.m_position01.m_segment == m_conf.Debug.EndSegmentId + || data.m_position03.m_segment == m_conf.Debug.EndSegmentId) + && (m_conf.Debug.VehicleId == 0 + || m_queueItem.vehicleId == m_conf.Debug.VehicleId); + if (m_debug) { + m_debugPositions = new Dictionary>(); + } #endif - int posCount = m_pathUnits.m_buffer[unit].m_positionCount & 0xF; - int vehiclePosIndicator = m_pathUnits.m_buffer[unit].m_positionCount >> 4; - BufferItem bufferItemStartA = default(BufferItem); - if (data.m_position00.m_segment != 0 && posCount >= 1) { + int posCount = m_pathUnits.m_buffer[unit].m_positionCount & 0xF; + int vehiclePosIndicator = m_pathUnits.m_buffer[unit].m_positionCount >> 4; + BufferItem bufferItemStartA = default(BufferItem); + if (data.m_position00.m_segment != 0 && posCount >= 1) { #if PARKINGAI || JUNCTIONRESTRICTIONS - m_startSegmentA = data.m_position00.m_segment; // NON-STOCK CODE + m_startSegmentA = data.m_position00.m_segment; // NON-STOCK CODE #endif - m_startLaneA = PathManager.GetLaneID(data.m_position00); - m_startOffsetA = data.m_position00.m_offset; - bufferItemStartA.m_laneID = m_startLaneA; - bufferItemStartA.m_position = data.m_position00; - GetLaneDirection(data.m_position00, out bufferItemStartA.m_direction, out bufferItemStartA.m_lanesUsed + m_startLaneA = PathManager.GetLaneID(data.m_position00); + m_startOffsetA = data.m_position00.m_offset; + bufferItemStartA.m_laneID = m_startLaneA; + bufferItemStartA.m_position = data.m_position00; + GetLaneDirection(data.m_position00, out bufferItemStartA.m_direction, out bufferItemStartA.m_lanesUsed #if PARKINGAI - , out bufferItemStartA.m_vehiclesUsed + , out bufferItemStartA.m_vehiclesUsed #endif - ); - bufferItemStartA.m_comparisonValue = 0f; - bufferItemStartA.m_duration = 0f; - } else { + ); + bufferItemStartA.m_comparisonValue = 0f; + bufferItemStartA.m_duration = 0f; + } else { #if PARKINGAI || JUNCTIONRESTRICTIONS - m_startSegmentA = 0; // NON-STOCK CODE + m_startSegmentA = 0; // NON-STOCK CODE #endif - m_startLaneA = 0u; - m_startOffsetA = 0; - } + m_startLaneA = 0u; + m_startOffsetA = 0; + } - BufferItem bufferItemStartB = default(BufferItem); - if (data.m_position02.m_segment != 0 && posCount >= 3) { + BufferItem bufferItemStartB = default(BufferItem); + if (data.m_position02.m_segment != 0 && posCount >= 3) { #if PARKINGAI || JUNCTIONRESTRICTIONS - m_startSegmentB = data.m_position02.m_segment; // NON-STOCK CODE + m_startSegmentB = data.m_position02.m_segment; // NON-STOCK CODE #endif - m_startLaneB = PathManager.GetLaneID(data.m_position02); - m_startOffsetB = data.m_position02.m_offset; - bufferItemStartB.m_laneID = m_startLaneB; - bufferItemStartB.m_position = data.m_position02; - GetLaneDirection(data.m_position02, out bufferItemStartB.m_direction, out bufferItemStartB.m_lanesUsed + m_startLaneB = PathManager.GetLaneID(data.m_position02); + m_startOffsetB = data.m_position02.m_offset; + bufferItemStartB.m_laneID = m_startLaneB; + bufferItemStartB.m_position = data.m_position02; + GetLaneDirection(data.m_position02, out bufferItemStartB.m_direction, out bufferItemStartB.m_lanesUsed #if PARKINGAI - , out bufferItemStartB.m_vehiclesUsed + , out bufferItemStartB.m_vehiclesUsed #endif - ); - bufferItemStartB.m_comparisonValue = 0f; - bufferItemStartB.m_duration = 0f; - } else { + ); + bufferItemStartB.m_comparisonValue = 0f; + bufferItemStartB.m_duration = 0f; + } else { #if PARKINGAI || JUNCTIONRESTRICTIONS - m_startSegmentB = 0; // NON-STOCK CODE -#endif - m_startLaneB = 0u; - m_startOffsetB = 0; - } - - BufferItem bufferItemEndA = default(BufferItem); - if (data.m_position01.m_segment != 0 && posCount >= 2) { - m_endLaneA = PathManager.GetLaneID(data.m_position01); - bufferItemEndA.m_laneID = m_endLaneA; - bufferItemEndA.m_position = data.m_position01; - GetLaneDirection(data.m_position01, out bufferItemEndA.m_direction, out bufferItemEndA.m_lanesUsed + m_startSegmentB = 0; // NON-STOCK CODE +#endif + m_startLaneB = 0u; + m_startOffsetB = 0; + } + + BufferItem bufferItemEndA = default(BufferItem); + if (data.m_position01.m_segment != 0 && posCount >= 2) { + m_endLaneA = PathManager.GetLaneID(data.m_position01); + bufferItemEndA.m_laneID = m_endLaneA; + bufferItemEndA.m_position = data.m_position01; + GetLaneDirection(data.m_position01, out bufferItemEndA.m_direction, out bufferItemEndA.m_lanesUsed #if PARKINGAI - , out bufferItemEndA.m_vehiclesUsed -#endif - ); - bufferItemEndA.m_methodDistance = 0.01f; - bufferItemEndA.m_comparisonValue = 0f; - bufferItemEndA.m_duration = 0f; - } else { - m_endLaneA = 0u; - } - - BufferItem bufferItemEndB = default(BufferItem); - if (data.m_position03.m_segment != 0 && posCount >= 4) { - m_endLaneB = PathManager.GetLaneID(data.m_position03); - bufferItemEndB.m_laneID = m_endLaneB; - bufferItemEndB.m_position = data.m_position03; - GetLaneDirection(data.m_position03, out bufferItemEndB.m_direction, out bufferItemEndB.m_lanesUsed + , out bufferItemEndA.m_vehiclesUsed +#endif + ); + bufferItemEndA.m_methodDistance = 0.01f; + bufferItemEndA.m_comparisonValue = 0f; + bufferItemEndA.m_duration = 0f; + } else { + m_endLaneA = 0u; + } + + BufferItem bufferItemEndB = default(BufferItem); + if (data.m_position03.m_segment != 0 && posCount >= 4) { + m_endLaneB = PathManager.GetLaneID(data.m_position03); + bufferItemEndB.m_laneID = m_endLaneB; + bufferItemEndB.m_position = data.m_position03; + GetLaneDirection(data.m_position03, out bufferItemEndB.m_direction, out bufferItemEndB.m_lanesUsed #if PARKINGAI - , out bufferItemEndB.m_vehiclesUsed -#endif - ); - bufferItemEndB.m_methodDistance = 0.01f; - bufferItemEndB.m_comparisonValue = 0f; - bufferItemEndB.m_duration = 0f; - } else { - m_endLaneB = 0u; - } - - if (data.m_position11.m_segment != 0 && vehiclePosIndicator >= 1) { - m_vehicleLane = PathManager.GetLaneID(data.m_position11); - m_vehicleOffset = data.m_position11.m_offset; - } else { - m_vehicleLane = 0u; - m_vehicleOffset = 0; - } - -#if DEBUG - bool detourMissing = (m_vehicleTypes & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail | VehicleInfo.VehicleType.Metro)) != VehicleInfo.VehicleType.None && !m_queueItem.queued; - if (detourMissing) { - Log.Warning($"Path-finding for unhandled vehicle requested!"); - } - - if (m_debug || detourMissing) { - Debug(unit, $"PathFindImplementation: Preparing calculation:\n" + - $"\tbufferItemStartA: segment={bufferItemStartA.m_position.m_segment} lane={bufferItemStartA.m_position.m_lane} off={bufferItemStartA.m_position.m_offset} laneId={bufferItemStartA.m_laneID}\n" + - $"\tbufferItemStartB: segment={bufferItemStartB.m_position.m_segment} lane={bufferItemStartB.m_position.m_lane} off={bufferItemStartB.m_position.m_offset} laneId={bufferItemStartB.m_laneID}\n" + - $"\tbufferItemEndA: segment={bufferItemEndA.m_position.m_segment} lane={bufferItemEndA.m_position.m_lane} off={bufferItemEndA.m_position.m_offset} laneId={bufferItemEndA.m_laneID}\n" + - $"\tbufferItemEndB: segment={bufferItemEndB.m_position.m_segment} lane={bufferItemEndB.m_position.m_lane} off={bufferItemEndB.m_position.m_offset} laneId={bufferItemEndB.m_laneID}\n" + - $"\tvehicleItem: segment={data.m_position11.m_segment} lane={data.m_position11.m_lane} off={data.m_position11.m_offset} laneId={m_vehicleLane} vehiclePosIndicator={vehiclePosIndicator}\n" + - $"Properties:\n" + - "\t" + $"m_maxLength={m_maxLength}\n" + - "\t" + $"m_startLaneA={m_startLaneA}\n" + - "\t" + $"m_startLaneB={m_startLaneB}\n" + - "\t" + $"m_endLaneA={m_endLaneA}\n" + - "\t" + $"m_endLaneB={m_endLaneB}\n" + - "\t" + $"m_startOffsetA={m_startOffsetA}\n" + - "\t" + $"m_startOffsetB={m_startOffsetB}\n" + - "\t" + $"m_vehicleLane={m_vehicleLane}\n" + - "\t" + $"m_vehicleOffset={m_vehicleOffset}\n" + - "\t" + $"m_carBanMask={m_carBanMask}\n" + - "\t" + $"m_disableMask={m_disableMask}\n" + - "\t" + $"m_ignoreBlocked={m_ignoreBlocked}\n" + - "\t" + $"m_stablePath={m_stablePath}\n" + - "\t" + $"m_randomParking={m_randomParking}\n" + - "\t" + $"m_transportVehicle={m_transportVehicle}\n" + - "\t" + $"m_ignoreCost={m_ignoreCost}\n" + - "\t" + $"m_pathFindIndex={m_pathFindIndex}\n" + - "\t" + $"m_laneTypes={m_laneTypes}\n" + - "\t" + $"m_vehicleTypes={m_vehicleTypes}\n" + - "\t" + $"m_queueItem={m_queueItem}\n" + - "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" + - "\t" + $"m_failedPathFinds={m_failedPathFinds}\n" + - "\t" + $"m_succeededPathFinds={m_succeededPathFinds}\n" + + , out bufferItemEndB.m_vehiclesUsed +#endif + ); + bufferItemEndB.m_methodDistance = 0.01f; + bufferItemEndB.m_comparisonValue = 0f; + bufferItemEndB.m_duration = 0f; + } else { + m_endLaneB = 0u; + } + + if (data.m_position11.m_segment != 0 && vehiclePosIndicator >= 1) { + m_vehicleLane = PathManager.GetLaneID(data.m_position11); + m_vehicleOffset = data.m_position11.m_offset; + } else { + m_vehicleLane = 0u; + m_vehicleOffset = 0; + } + +#if DEBUG + bool detourMissing = (m_vehicleTypes & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail | VehicleInfo.VehicleType.Metro)) != VehicleInfo.VehicleType.None && !m_queueItem.queued; + if (detourMissing) { + Log.Warning($"Path-finding for unhandled vehicle requested!"); + } + + if (m_debug || detourMissing) { + Debug(unit, $"PathFindImplementation: Preparing calculation:\n" + + $"\tbufferItemStartA: segment={bufferItemStartA.m_position.m_segment} lane={bufferItemStartA.m_position.m_lane} off={bufferItemStartA.m_position.m_offset} laneId={bufferItemStartA.m_laneID}\n" + + $"\tbufferItemStartB: segment={bufferItemStartB.m_position.m_segment} lane={bufferItemStartB.m_position.m_lane} off={bufferItemStartB.m_position.m_offset} laneId={bufferItemStartB.m_laneID}\n" + + $"\tbufferItemEndA: segment={bufferItemEndA.m_position.m_segment} lane={bufferItemEndA.m_position.m_lane} off={bufferItemEndA.m_position.m_offset} laneId={bufferItemEndA.m_laneID}\n" + + $"\tbufferItemEndB: segment={bufferItemEndB.m_position.m_segment} lane={bufferItemEndB.m_position.m_lane} off={bufferItemEndB.m_position.m_offset} laneId={bufferItemEndB.m_laneID}\n" + + $"\tvehicleItem: segment={data.m_position11.m_segment} lane={data.m_position11.m_lane} off={data.m_position11.m_offset} laneId={m_vehicleLane} vehiclePosIndicator={vehiclePosIndicator}\n" + + $"Properties:\n" + + "\t" + $"m_maxLength={m_maxLength}\n" + + "\t" + $"m_startLaneA={m_startLaneA}\n" + + "\t" + $"m_startLaneB={m_startLaneB}\n" + + "\t" + $"m_endLaneA={m_endLaneA}\n" + + "\t" + $"m_endLaneB={m_endLaneB}\n" + + "\t" + $"m_startOffsetA={m_startOffsetA}\n" + + "\t" + $"m_startOffsetB={m_startOffsetB}\n" + + "\t" + $"m_vehicleLane={m_vehicleLane}\n" + + "\t" + $"m_vehicleOffset={m_vehicleOffset}\n" + + "\t" + $"m_carBanMask={m_carBanMask}\n" + + "\t" + $"m_disableMask={m_disableMask}\n" + + "\t" + $"m_ignoreBlocked={m_ignoreBlocked}\n" + + "\t" + $"m_stablePath={m_stablePath}\n" + + "\t" + $"m_randomParking={m_randomParking}\n" + + "\t" + $"m_transportVehicle={m_transportVehicle}\n" + + "\t" + $"m_ignoreCost={m_ignoreCost}\n" + + "\t" + $"m_pathFindIndex={m_pathFindIndex}\n" + + "\t" + $"m_laneTypes={m_laneTypes}\n" + + "\t" + $"m_vehicleTypes={m_vehicleTypes}\n" + + "\t" + $"m_queueItem={m_queueItem}\n" + + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"m_failedPathFinds={m_failedPathFinds}\n" + + "\t" + $"m_succeededPathFinds={m_succeededPathFinds}\n" + #if PARKINGAI || JUNCTIONRESTRICTIONS - "\t" + $"m_startSegmentA={m_startSegmentA}\n" + - "\t" + $"m_startSegmentB={m_startSegmentB}\n" + + "\t" + $"m_startSegmentA={m_startSegmentA}\n" + + "\t" + $"m_startSegmentB={m_startSegmentB}\n" + #endif #if ROUTING - "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + - "\t" + $"m_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}" -#endif - ); - } -#endif - - BufferItem finalBufferItem = default(BufferItem); - byte startOffset = 0; - m_bufferMinPos = 0; - m_bufferMaxPos = -1; - - if (m_pathFindIndex == 0) { - uint num3 = 4294901760u; - for (int i = 0; i < 262144; i++) { - m_laneLocation[i] = num3; - } - } - - for (int j = 0; j < 1024; j++) { - m_bufferMin[j] = 0; - m_bufferMax[j] = -1; - } - - if (bufferItemEndA.m_position.m_segment != 0) { - m_bufferMax[0]++; - m_buffer[++m_bufferMaxPos] = bufferItemEndA; - } - - if (bufferItemEndB.m_position.m_segment != 0) { - m_bufferMax[0]++; - m_buffer[++m_bufferMaxPos] = bufferItemEndB; - } - - bool canFindPath = false; - while (m_bufferMinPos <= m_bufferMaxPos) { - int bufMin = m_bufferMin[m_bufferMinPos]; - int bufMax = m_bufferMax[m_bufferMinPos]; - - if (bufMin > bufMax) { - m_bufferMinPos++; - } else { - m_bufferMin[m_bufferMinPos] = bufMin + 1; - BufferItem candidateItem = m_buffer[(m_bufferMinPos << 6) + bufMin]; - if (candidateItem.m_position.m_segment == bufferItemStartA.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartA.m_position.m_lane) { - if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetA) { - finalBufferItem = candidateItem; - startOffset = m_startOffsetA; - canFindPath = true; - break; - } - - if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetA) { - finalBufferItem = candidateItem; - startOffset = m_startOffsetA; - canFindPath = true; - break; - } - } - - if (candidateItem.m_position.m_segment == bufferItemStartB.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartB.m_position.m_lane) { - if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetB) { - finalBufferItem = candidateItem; - startOffset = m_startOffsetB; - canFindPath = true; - break; - } - - if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetB) { - finalBufferItem = candidateItem; - startOffset = m_startOffsetB; - canFindPath = true; - break; - } - } - - ushort startNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; - ushort endNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; - - if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) { - ProcessItemMain( -#if DEBUG - unit, -#endif - candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], startNodeId, ref netManager.m_nodes.m_buffer[startNodeId], 0, false); - } - - if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None) { - ProcessItemMain( -#if DEBUG - unit, -#endif - candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], endNodeId, ref netManager.m_nodes.m_buffer[endNodeId], 255, false); - } - - int numIter = 0; - ushort specialNodeId = netManager.m_lanes.m_buffer[candidateItem.m_laneID].m_nodes; - if (specialNodeId != 0) { - bool nodesDisabled = ((netManager.m_nodes.m_buffer[startNodeId].m_flags | netManager.m_nodes.m_buffer[endNodeId].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None; - - while (specialNodeId != 0) { - NetInfo.Direction direction = NetInfo.Direction.None; - byte laneOffset = netManager.m_nodes.m_buffer[specialNodeId].m_laneOffset; - - if (laneOffset <= candidateItem.m_position.m_offset) { - direction |= NetInfo.Direction.Forward; - } - - if (laneOffset >= candidateItem.m_position.m_offset) { - direction |= NetInfo.Direction.Backward; - } - - if ((candidateItem.m_direction & direction) != NetInfo.Direction.None && (!nodesDisabled || (netManager.m_nodes.m_buffer[specialNodeId].m_flags & NetNode.Flags.Disabled) != NetNode.Flags.None)) { -#if DEBUG - if (m_debug && (m_conf.Debug.NodeId <= 0 || specialNodeId == m_conf.Debug.NodeId)) { - Debug(unit, $"PathFindImplementation: Handling special node for path unit {unit}, type {m_queueItem.vehicleType}:\n" + - $"\tcandidateItem.m_position.m_segment={candidateItem.m_position.m_segment}\n" + - $"\tcandidateItem.m_position.m_lane={candidateItem.m_position.m_lane}\n" + - $"\tcandidateItem.m_laneID={candidateItem.m_laneID}\n" + - $"\tspecialNodeId={specialNodeId}\n" + - $"\tstartNodeId={startNodeId}\n" + - $"\tendNodeId={endNodeId}\n" - ); - } -#endif - ProcessItemMain( -#if DEBUG - unit, -#endif - candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], specialNodeId, ref netManager.m_nodes.m_buffer[specialNodeId], laneOffset, true); - } - - specialNodeId = netManager.m_nodes.m_buffer[specialNodeId].m_nextLaneNode; - - if (++numIter == 32768) { - break; - } - } - } - } - } - - if (!canFindPath) { - m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; - // NON-STOCK CODE START -#if DEBUG - ++m_failedPathFinds; - - if (m_debug) { - Debug(unit, $"PathFindImplementation: Path-find failed: Could not find path"); - string reachableBuf = ""; - string unreachableBuf = ""; - foreach (KeyValuePair> e in m_debugPositions) { - string buf = $"{e.Key} -> {e.Value.CollectionToString()}\n"; - if (e.Value.Count <= 0) { - unreachableBuf += buf; - } else { - reachableBuf += buf; - } - } - Debug(unit, $"PathFindImplementation: Reachability graph:\n== REACHABLE ==\n" + reachableBuf + "\n== UNREACHABLE ==\n" + unreachableBuf); - } -#endif - // NON-STOCK CODE END - } else { - float duration = (m_laneTypes != NetInfo.LaneType.Pedestrian && (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None) ? finalBufferItem.m_duration : finalBufferItem.m_methodDistance; - m_pathUnits.m_buffer[unit].m_length = duration; - m_pathUnits.m_buffer[unit].m_speed = (byte)Mathf.Clamp(finalBufferItem.m_methodDistance * 100f / Mathf.Max(0.01f, finalBufferItem.m_duration), 0f, 255f); + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"m_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}" +#endif + ); + } +#endif + + BufferItem finalBufferItem = default(BufferItem); + byte startOffset = 0; + m_bufferMinPos = 0; + m_bufferMaxPos = -1; + + if (m_pathFindIndex == 0) { + uint num3 = 4294901760u; + for (int i = 0; i < 262144; i++) { + m_laneLocation[i] = num3; + } + } + + for (int j = 0; j < 1024; j++) { + m_bufferMin[j] = 0; + m_bufferMax[j] = -1; + } + + if (bufferItemEndA.m_position.m_segment != 0) { + m_bufferMax[0]++; + m_buffer[++m_bufferMaxPos] = bufferItemEndA; + } + + if (bufferItemEndB.m_position.m_segment != 0) { + m_bufferMax[0]++; + m_buffer[++m_bufferMaxPos] = bufferItemEndB; + } + + bool canFindPath = false; + while (m_bufferMinPos <= m_bufferMaxPos) { + int bufMin = m_bufferMin[m_bufferMinPos]; + int bufMax = m_bufferMax[m_bufferMinPos]; + + if (bufMin > bufMax) { + m_bufferMinPos++; + } else { + m_bufferMin[m_bufferMinPos] = bufMin + 1; + BufferItem candidateItem = m_buffer[(m_bufferMinPos << 6) + bufMin]; + if (candidateItem.m_position.m_segment == bufferItemStartA.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartA.m_position.m_lane) { + if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetA) { + finalBufferItem = candidateItem; + startOffset = m_startOffsetA; + canFindPath = true; + break; + } + + if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetA) { + finalBufferItem = candidateItem; + startOffset = m_startOffsetA; + canFindPath = true; + break; + } + } + + if (candidateItem.m_position.m_segment == bufferItemStartB.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartB.m_position.m_lane) { + if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetB) { + finalBufferItem = candidateItem; + startOffset = m_startOffsetB; + canFindPath = true; + break; + } + + if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetB) { + finalBufferItem = candidateItem; + startOffset = m_startOffsetB; + canFindPath = true; + break; + } + } + + ushort startNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; + ushort endNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; + + if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) { + ProcessItemMain( +#if DEBUG + unit, +#endif + candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], startNodeId, ref netManager.m_nodes.m_buffer[startNodeId], 0, false); + } + + if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None) { + ProcessItemMain( +#if DEBUG + unit, +#endif + candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], endNodeId, ref netManager.m_nodes.m_buffer[endNodeId], 255, false); + } + + int numIter = 0; + ushort specialNodeId = netManager.m_lanes.m_buffer[candidateItem.m_laneID].m_nodes; + if (specialNodeId != 0) { + bool nodesDisabled = ((netManager.m_nodes.m_buffer[startNodeId].m_flags | netManager.m_nodes.m_buffer[endNodeId].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None; + + while (specialNodeId != 0) { + NetInfo.Direction direction = NetInfo.Direction.None; + byte laneOffset = netManager.m_nodes.m_buffer[specialNodeId].m_laneOffset; + + if (laneOffset <= candidateItem.m_position.m_offset) { + direction |= NetInfo.Direction.Forward; + } + + if (laneOffset >= candidateItem.m_position.m_offset) { + direction |= NetInfo.Direction.Backward; + } + + if ((candidateItem.m_direction & direction) != NetInfo.Direction.None && (!nodesDisabled || (netManager.m_nodes.m_buffer[specialNodeId].m_flags & NetNode.Flags.Disabled) != NetNode.Flags.None)) { +#if DEBUG + if (m_debug && (m_conf.Debug.NodeId <= 0 || specialNodeId == m_conf.Debug.NodeId)) { + Debug(unit, $"PathFindImplementation: Handling special node for path unit {unit}, type {m_queueItem.vehicleType}:\n" + + $"\tcandidateItem.m_position.m_segment={candidateItem.m_position.m_segment}\n" + + $"\tcandidateItem.m_position.m_lane={candidateItem.m_position.m_lane}\n" + + $"\tcandidateItem.m_laneID={candidateItem.m_laneID}\n" + + $"\tspecialNodeId={specialNodeId}\n" + + $"\tstartNodeId={startNodeId}\n" + + $"\tendNodeId={endNodeId}\n" + ); + } +#endif + ProcessItemMain( +#if DEBUG + unit, +#endif + candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], specialNodeId, ref netManager.m_nodes.m_buffer[specialNodeId], laneOffset, true); + } + + specialNodeId = netManager.m_nodes.m_buffer[specialNodeId].m_nextLaneNode; + + if (++numIter == 32768) { + break; + } + } + } + } + } + + if (!canFindPath) { + m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; + // NON-STOCK CODE START +#if DEBUG + ++m_failedPathFinds; + + if (m_debug) { + Debug(unit, $"PathFindImplementation: Path-find failed: Could not find path"); + string reachableBuf = ""; + string unreachableBuf = ""; + foreach (KeyValuePair> e in m_debugPositions) { + string buf = $"{e.Key} -> {e.Value.CollectionToString()}\n"; + if (e.Value.Count <= 0) { + unreachableBuf += buf; + } else { + reachableBuf += buf; + } + } + Debug(unit, $"PathFindImplementation: Reachability graph:\n== REACHABLE ==\n" + reachableBuf + "\n== UNREACHABLE ==\n" + unreachableBuf); + } +#endif + // NON-STOCK CODE END + } else { + float duration = (m_laneTypes != NetInfo.LaneType.Pedestrian && (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None) ? finalBufferItem.m_duration : finalBufferItem.m_methodDistance; + m_pathUnits.m_buffer[unit].m_length = duration; + m_pathUnits.m_buffer[unit].m_speed = (byte)Mathf.Clamp(finalBufferItem.m_methodDistance * 100f / Mathf.Max(0.01f, finalBufferItem.m_duration), 0f, 255f); #if PARKINGAI - m_pathUnits.m_buffer[unit].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; - m_pathUnits.m_buffer[unit].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; -#endif - - uint currentPathUnitId = unit; - int currentItemPositionCount = 0; - int sumOfPositionCounts = 0; - PathUnit.Position currentPosition = finalBufferItem.m_position; - - if ((currentPosition.m_segment != bufferItemEndA.m_position.m_segment || currentPosition.m_lane != bufferItemEndA.m_position.m_lane || currentPosition.m_offset != bufferItemEndA.m_position.m_offset) && - (currentPosition.m_segment != bufferItemEndB.m_position.m_segment || currentPosition.m_lane != bufferItemEndB.m_position.m_lane || currentPosition.m_offset != bufferItemEndB.m_position.m_offset)) { - if (startOffset != currentPosition.m_offset) { - PathUnit.Position position2 = currentPosition; - position2.m_offset = startOffset; - m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, position2); - } - - m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); - currentPosition = m_laneTarget[finalBufferItem.m_laneID]; - } - - for (int k = 0; k < 262144; k++) { - m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); - - if ((currentPosition.m_segment == bufferItemEndA.m_position.m_segment && currentPosition.m_lane == bufferItemEndA.m_position.m_lane && currentPosition.m_offset == bufferItemEndA.m_position.m_offset) || - (currentPosition.m_segment == bufferItemEndB.m_position.m_segment && currentPosition.m_lane == bufferItemEndB.m_position.m_lane && currentPosition.m_offset == bufferItemEndB.m_position.m_offset)) { - m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; - sumOfPositionCounts += currentItemPositionCount; - if (sumOfPositionCounts != 0) { - currentPathUnitId = m_pathUnits.m_buffer[unit].m_nextPathUnit; - currentItemPositionCount = m_pathUnits.m_buffer[unit].m_positionCount; - int numIter = 0; - while (currentPathUnitId != 0) { - m_pathUnits.m_buffer[currentPathUnitId].m_length = duration * (float)(sumOfPositionCounts - currentItemPositionCount) / (float)sumOfPositionCounts; - currentItemPositionCount += m_pathUnits.m_buffer[currentPathUnitId].m_positionCount; - currentPathUnitId = m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit; - if (++numIter >= 262144) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } - m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_READY; - // NON-STOCK CODE START -#if DEBUG - ++m_succeededPathFinds; - - if (m_debug) { - Debug(unit, $"PathFindImplementation: Path-find succeeded"); - } -#endif - // NON-STOCK CODE END - return; - } - - if (currentItemPositionCount == 12) { - uint createdPathUnitId; - try { - Monitor.Enter(m_bufferLock); - - if (!m_pathUnits.CreateItem(out createdPathUnitId, ref m_pathRandomizer)) { - m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; - // NON-STOCK CODE START -#if DEBUG - ++m_failedPathFinds; - - if (m_debug) { - Debug(unit, $"Path-finding failed: Could not create path unit"); - } -#endif - // NON-STOCK CODE END - return; - } - - m_pathUnits.m_buffer[createdPathUnitId] = m_pathUnits.m_buffer[currentPathUnitId]; - m_pathUnits.m_buffer[createdPathUnitId].m_referenceCount = 1; - m_pathUnits.m_buffer[createdPathUnitId].m_pathFindFlags = PathUnit.FLAG_READY; - m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit = createdPathUnitId; - m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; + m_pathUnits.m_buffer[unit].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; + m_pathUnits.m_buffer[unit].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; +#endif + + uint currentPathUnitId = unit; + int currentItemPositionCount = 0; + int sumOfPositionCounts = 0; + PathUnit.Position currentPosition = finalBufferItem.m_position; + + if ((currentPosition.m_segment != bufferItemEndA.m_position.m_segment || currentPosition.m_lane != bufferItemEndA.m_position.m_lane || currentPosition.m_offset != bufferItemEndA.m_position.m_offset) && + (currentPosition.m_segment != bufferItemEndB.m_position.m_segment || currentPosition.m_lane != bufferItemEndB.m_position.m_lane || currentPosition.m_offset != bufferItemEndB.m_position.m_offset)) { + if (startOffset != currentPosition.m_offset) { + PathUnit.Position position2 = currentPosition; + position2.m_offset = startOffset; + m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, position2); + } + + m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); + currentPosition = m_laneTarget[finalBufferItem.m_laneID]; + } + + for (int k = 0; k < 262144; k++) { + m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); + + if ((currentPosition.m_segment == bufferItemEndA.m_position.m_segment && currentPosition.m_lane == bufferItemEndA.m_position.m_lane && currentPosition.m_offset == bufferItemEndA.m_position.m_offset) || + (currentPosition.m_segment == bufferItemEndB.m_position.m_segment && currentPosition.m_lane == bufferItemEndB.m_position.m_lane && currentPosition.m_offset == bufferItemEndB.m_position.m_offset)) { + m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; + sumOfPositionCounts += currentItemPositionCount; + if (sumOfPositionCounts != 0) { + currentPathUnitId = m_pathUnits.m_buffer[unit].m_nextPathUnit; + currentItemPositionCount = m_pathUnits.m_buffer[unit].m_positionCount; + int numIter = 0; + while (currentPathUnitId != 0) { + m_pathUnits.m_buffer[currentPathUnitId].m_length = duration * (float)(sumOfPositionCounts - currentItemPositionCount) / (float)sumOfPositionCounts; + currentItemPositionCount += m_pathUnits.m_buffer[currentPathUnitId].m_positionCount; + currentPathUnitId = m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit; + if (++numIter >= 262144) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } + m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_READY; + // NON-STOCK CODE START +#if DEBUG + ++m_succeededPathFinds; + + if (m_debug) { + Debug(unit, $"PathFindImplementation: Path-find succeeded"); + } +#endif + // NON-STOCK CODE END + return; + } + + if (currentItemPositionCount == 12) { + uint createdPathUnitId; + try { + Monitor.Enter(m_bufferLock); + + if (!m_pathUnits.CreateItem(out createdPathUnitId, ref m_pathRandomizer)) { + m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; + // NON-STOCK CODE START +#if DEBUG + ++m_failedPathFinds; + + if (m_debug) { + Debug(unit, $"Path-finding failed: Could not create path unit"); + } +#endif + // NON-STOCK CODE END + return; + } + + m_pathUnits.m_buffer[createdPathUnitId] = m_pathUnits.m_buffer[currentPathUnitId]; + m_pathUnits.m_buffer[createdPathUnitId].m_referenceCount = 1; + m_pathUnits.m_buffer[createdPathUnitId].m_pathFindFlags = PathUnit.FLAG_READY; + m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit = createdPathUnitId; + m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; #if PARKINGAI - m_pathUnits.m_buffer[currentPathUnitId].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // (this is not accurate!) - m_pathUnits.m_buffer[currentPathUnitId].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // (this is not accurate!) -#endif - sumOfPositionCounts += currentItemPositionCount; - Singleton.instance.m_pathUnitCount = (int)(m_pathUnits.ItemCount() - 1); - } finally { - Monitor.Exit(m_bufferLock); - } - - currentPathUnitId = createdPathUnitId; - currentItemPositionCount = 0; - } - - uint laneID = PathManager.GetLaneID(currentPosition); - currentPosition = m_laneTarget[laneID]; - } - - m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; - // NON-STOCK CODE START -#if DEBUG - ++m_failedPathFinds; - - if (m_debug) { - Debug(unit, $"Path-finding failed: Internal loop break error"); - } -#endif - // NON-STOCK CODE END - } - } - -#if DEBUG - private void Debug(uint unit, string message) { - Log._Debug( - $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" - + $"UNIT({unit})\n" - + message - ); - } - - private void Debug(uint unit, BufferItem item, string message) { - Log._Debug( - $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" - + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane})\n" - + $"ITEM({item})\n" - + message - ); - } - - private void Debug(uint unit, BufferItem item, ushort nextSegmentId, string message) { - Log._Debug( - $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" - + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane}) -> s#({nextSegmentId})\n" - + $"ITEM({item})\n" - + message - ); - } - - private void Debug(uint unit, BufferItem item, ushort nextSegmentId, int nextLaneIndex, uint nextLaneId, string message) { - Log._Debug( - $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" - + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane}) -> s#({nextSegmentId}), l#({nextLaneIndex}), lid#({nextLaneId})\n" - + $"ITEM({item})\n" - + message - ); - } -#endif - - // 1 - private void ProcessItemMain( -#if DEBUG - uint unitId, -#endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, ushort nextNodeId, ref NetNode nextNode, byte connectOffset, bool isMiddle) { -#if DEBUG - bool debug = this.m_debug && (m_conf.Debug.NodeId <= 0 || nextNodeId == m_conf.Debug.NodeId); - if (debug) { - if (!m_debugPositions.ContainsKey(item.m_position.m_segment)) { - m_debugPositions[item.m_position.m_segment] = new List(); - } - } - - if (debug) { - Debug(unitId, item, $"ProcessItemMain called.\n" - + "\t" + $"nextNodeId={nextNodeId}\n" - + "\t" + $"connectOffset={connectOffset}\n" - + "\t" + $"isMiddle={isMiddle}" - ); - } -#endif - - NetManager netManager = Singleton.instance; - - ushort prevSegmentId = item.m_position.m_segment; - byte prevLaneIndex = item.m_position.m_lane; - - bool prevIsPedestrianLane = false; - bool prevIsBicycleLane = false; - bool prevIsCenterPlatform = false; - bool prevIsElevated = false; + m_pathUnits.m_buffer[currentPathUnitId].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // (this is not accurate!) + m_pathUnits.m_buffer[currentPathUnitId].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // (this is not accurate!) +#endif + sumOfPositionCounts += currentItemPositionCount; + Singleton.instance.m_pathUnitCount = (int)(m_pathUnits.ItemCount() - 1); + } finally { + Monitor.Exit(m_bufferLock); + } + + currentPathUnitId = createdPathUnitId; + currentItemPositionCount = 0; + } + + uint laneID = PathManager.GetLaneID(currentPosition); + currentPosition = m_laneTarget[laneID]; + } + + m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; + // NON-STOCK CODE START +#if DEBUG + ++m_failedPathFinds; + + if (m_debug) { + Debug(unit, $"Path-finding failed: Internal loop break error"); + } +#endif + // NON-STOCK CODE END + } + } + +#if DEBUG + private void Debug(uint unit, string message) { + Log._Debug( + $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + + $"UNIT({unit})\n" + + message + ); + } + + private void Debug(uint unit, BufferItem item, string message) { + Log._Debug( + $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane})\n" + + $"ITEM({item})\n" + + message + ); + } + + private void Debug(uint unit, BufferItem item, ushort nextSegmentId, string message) { + Log._Debug( + $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane}) -> s#({nextSegmentId})\n" + + $"ITEM({item})\n" + + message + ); + } + + private void Debug(uint unit, BufferItem item, ushort nextSegmentId, int nextLaneIndex, uint nextLaneId, string message) { + Log._Debug( + $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane}) -> s#({nextSegmentId}), l#({nextLaneIndex}), lid#({nextLaneId})\n" + + $"ITEM({item})\n" + + message + ); + } +#endif + + // 1 + private void ProcessItemMain( +#if DEBUG + uint unitId, +#endif + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, ushort nextNodeId, ref NetNode nextNode, byte connectOffset, bool isMiddle) { +#if DEBUG + bool debug = this.m_debug && (m_conf.Debug.NodeId <= 0 || nextNodeId == m_conf.Debug.NodeId); + if (debug) { + if (!m_debugPositions.ContainsKey(item.m_position.m_segment)) { + m_debugPositions[item.m_position.m_segment] = new List(); + } + } + + if (debug) { + Debug(unitId, item, $"ProcessItemMain called.\n" + + "\t" + $"nextNodeId={nextNodeId}\n" + + "\t" + $"connectOffset={connectOffset}\n" + + "\t" + $"isMiddle={isMiddle}" + ); + } +#endif + + NetManager netManager = Singleton.instance; + + ushort prevSegmentId = item.m_position.m_segment; + byte prevLaneIndex = item.m_position.m_lane; + + bool prevIsPedestrianLane = false; + bool prevIsBicycleLane = false; + bool prevIsCenterPlatform = false; + bool prevIsElevated = false; #if ADVANCEDAI && ROUTING - // NON-STOCK CODE START - bool prevIsCarLane = false; - // NON-STOCK CODE END -#endif - int prevRelSimilarLaneIndex = 0; - // NON-STOCK CODE START - float prevMaxSpeed = 1f; - float prevLaneSpeed = 1f; - // NON-STOCK CODE END - - NetInfo prevSegmentInfo = prevSegment.Info; - if (prevLaneIndex < prevSegmentInfo.m_lanes.Length) { - NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; - prevIsPedestrianLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian); - prevIsBicycleLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (prevLaneInfo.m_vehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Bicycle); - prevIsCenterPlatform = prevLaneInfo.m_centerPlatform; - prevIsElevated = prevLaneInfo.m_elevated; + // NON-STOCK CODE START + bool prevIsCarLane = false; + // NON-STOCK CODE END +#endif + int prevRelSimilarLaneIndex = 0; + // NON-STOCK CODE START + float prevMaxSpeed = 1f; + float prevLaneSpeed = 1f; + // NON-STOCK CODE END + + NetInfo prevSegmentInfo = prevSegment.Info; + if (prevLaneIndex < prevSegmentInfo.m_lanes.Length) { + NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; + prevIsPedestrianLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian); + prevIsBicycleLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (prevLaneInfo.m_vehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Bicycle); + prevIsCenterPlatform = prevLaneInfo.m_centerPlatform; + prevIsElevated = prevLaneInfo.m_elevated; #if (ADVANCEDAI || PARKINGAI) && ROUTING - // NON-STOCK CODE START - prevIsCarLane = - (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && - (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None - ; - // NON-STOCK CODE END + // NON-STOCK CODE START + prevIsCarLane = + (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && + (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None + ; + // NON-STOCK CODE END #endif - // NON-STOCK CODE START + // NON-STOCK CODE START #if SPEEDLIMITS - prevMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(prevSegmentId, prevLaneIndex, item.m_laneID, prevLaneInfo); + prevMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(prevSegmentId, prevLaneIndex, item.m_laneID, prevLaneInfo); #else prevMaxSpeed = prevLaneInfo.m_speedLimit; #endif - prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); - // NON-STOCK CODE END - - prevRelSimilarLaneIndex = (((prevLaneInfo.m_finalDirection & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? (prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1) : prevLaneInfo.m_similarLaneIndex); - } - - if (isMiddle) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: middle: Exploring middle node\n" + - "\t" + $"nextNodeId={nextNodeId}" - ); - } -#endif - for (int i = 0; i < 8; i++) { - ushort nextSegmentId = nextNode.GetSegment(i); - if (nextSegmentId != 0) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: middle: Exploring next segment behind middle node\n" + - "\t" + $"nextSegmentId={nextSegmentId}"); - } -#endif - - ProcessItemCosts( -#if DEBUG - debug, unitId, -#endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, true, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsPedestrianLane, prevIsPedestrianLane - ); - } - } - } else if (prevIsPedestrianLane) { - // we are going to a pedestrian lane - if (!prevIsElevated) { - if (nextNode.Info.m_class.m_service != ItemClass.Service.Beautification) { - bool canCrossStreet = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; - bool isOnCenterPlatform = prevIsCenterPlatform && (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Junction)) == NetNode.Flags.None; - ushort nextLeftSegmentId = prevSegmentId; - ushort nextRightSegmentId = prevSegmentId; - int leftLaneIndex; - int rightLaneIndex; - uint leftLaneId; - uint rightLaneId; - - prevSegment.GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, prevLaneIndex, isOnCenterPlatform, out leftLaneIndex, out rightLaneIndex, out leftLaneId, out rightLaneId); - - if (leftLaneId == 0 || rightLaneId == 0) { - ushort leftSegmentId; - ushort rightSegmentId; - prevSegment.GetLeftAndRightSegments(nextNodeId, out leftSegmentId, out rightSegmentId); - - int numIter = 0; - while (leftSegmentId != 0 && leftSegmentId != prevSegmentId && leftLaneId == 0) { - int someLeftLaneIndex; - int someRightLaneIndex; - uint someLeftLaneId; - uint someRightLaneId; - netManager.m_segments.m_buffer[leftSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); - - if (someRightLaneId != 0) { - nextLeftSegmentId = leftSegmentId; - leftLaneIndex = someRightLaneIndex; - leftLaneId = someRightLaneId; - break; // NON-STOCK CODE - } else { + prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); + // NON-STOCK CODE END + + prevRelSimilarLaneIndex = (((prevLaneInfo.m_finalDirection & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? (prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1) : prevLaneInfo.m_similarLaneIndex); + } + + if (isMiddle) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: middle: Exploring middle node\n" + + "\t" + $"nextNodeId={nextNodeId}" + ); + } +#endif + for (int i = 0; i < 8; i++) { + ushort nextSegmentId = nextNode.GetSegment(i); + if (nextSegmentId != 0) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: middle: Exploring next segment behind middle node\n" + + "\t" + $"nextSegmentId={nextSegmentId}"); + } +#endif + + ProcessItemCosts( +#if DEBUG + debug, unitId, +#endif + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, true, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsPedestrianLane, prevIsPedestrianLane + ); + } + } + } else if (prevIsPedestrianLane) { + // we are going to a pedestrian lane + if (!prevIsElevated) { + if (nextNode.Info.m_class.m_service != ItemClass.Service.Beautification) { + bool canCrossStreet = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; + bool isOnCenterPlatform = prevIsCenterPlatform && (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Junction)) == NetNode.Flags.None; + ushort nextLeftSegmentId = prevSegmentId; + ushort nextRightSegmentId = prevSegmentId; + int leftLaneIndex; + int rightLaneIndex; + uint leftLaneId; + uint rightLaneId; + + prevSegment.GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, prevLaneIndex, isOnCenterPlatform, out leftLaneIndex, out rightLaneIndex, out leftLaneId, out rightLaneId); + + if (leftLaneId == 0 || rightLaneId == 0) { + ushort leftSegmentId; + ushort rightSegmentId; + prevSegment.GetLeftAndRightSegments(nextNodeId, out leftSegmentId, out rightSegmentId); + + int numIter = 0; + while (leftSegmentId != 0 && leftSegmentId != prevSegmentId && leftLaneId == 0) { + int someLeftLaneIndex; + int someRightLaneIndex; + uint someLeftLaneId; + uint someRightLaneId; + netManager.m_segments.m_buffer[leftSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); + + if (someRightLaneId != 0) { + nextLeftSegmentId = leftSegmentId; + leftLaneIndex = someRightLaneIndex; + leftLaneId = someRightLaneId; + break; // NON-STOCK CODE + } else { #if JUNCTIONRESTRICTIONS - // next segment does not have pedestrian lanes but cims need to cross it to reach the next segment - if (!m_junctionManager.IsPedestrianCrossingAllowed(leftSegmentId, netManager.m_segments.m_buffer[leftSegmentId].m_startNode == nextNodeId)) { - break; - } -#endif - leftSegmentId = netManager.m_segments.m_buffer[leftSegmentId].GetLeftSegment(nextNodeId); - } - - if (++numIter == 8) { - break; - } - } - - numIter = 0; - while (rightSegmentId != 0 && rightSegmentId != prevSegmentId && rightLaneId == 0) { - int someLeftLaneIndex; - int someRightLaneIndex; - uint someLeftLaneId; - uint someRightLaneId; - netManager.m_segments.m_buffer[rightSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); - - if (someLeftLaneId != 0) { - nextRightSegmentId = rightSegmentId; - rightLaneIndex = someLeftLaneIndex; - rightLaneId = someLeftLaneId; - break; // NON-STOCK CODE - } else { + // next segment does not have pedestrian lanes but cims need to cross it to reach the next segment + if (!m_junctionManager.IsPedestrianCrossingAllowed(leftSegmentId, netManager.m_segments.m_buffer[leftSegmentId].m_startNode == nextNodeId)) { + break; + } +#endif + leftSegmentId = netManager.m_segments.m_buffer[leftSegmentId].GetLeftSegment(nextNodeId); + } + + if (++numIter == 8) { + break; + } + } + + numIter = 0; + while (rightSegmentId != 0 && rightSegmentId != prevSegmentId && rightLaneId == 0) { + int someLeftLaneIndex; + int someRightLaneIndex; + uint someLeftLaneId; + uint someRightLaneId; + netManager.m_segments.m_buffer[rightSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); + + if (someLeftLaneId != 0) { + nextRightSegmentId = rightSegmentId; + rightLaneIndex = someLeftLaneIndex; + rightLaneId = someLeftLaneId; + break; // NON-STOCK CODE + } else { #if JUNCTIONRESTRICTIONS - // next segment does not have pedestrian lanes but cims need to cross it to reach the next segment - if (!m_junctionManager.IsPedestrianCrossingAllowed(rightSegmentId, netManager.m_segments.m_buffer[rightSegmentId].m_startNode == nextNodeId)) { - break; - } + // next segment does not have pedestrian lanes but cims need to cross it to reach the next segment + if (!m_junctionManager.IsPedestrianCrossingAllowed(rightSegmentId, netManager.m_segments.m_buffer[rightSegmentId].m_startNode == nextNodeId)) { + break; + } #endif - rightSegmentId = netManager.m_segments.m_buffer[rightSegmentId].GetRightSegment(nextNodeId); - } - - if (++numIter == 8) { - break; - } - } - } + rightSegmentId = netManager.m_segments.m_buffer[rightSegmentId].GetRightSegment(nextNodeId); + } + + if (++numIter == 8) { + break; + } + } + } - if (leftLaneId != 0 && (nextLeftSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> ped: Exploring left pedestrian lane\n" + - "\t" + $"leftLaneId={leftLaneId}\n" + - "\t" + $"nextLeftSegmentId={nextLeftSegmentId}\n" + - "\t" + $"canCrossStreet={canCrossStreet}\n" + - "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" - ); - } + if (leftLaneId != 0 && (nextLeftSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> ped: Exploring left pedestrian lane\n" + + "\t" + $"leftLaneId={leftLaneId}\n" + + "\t" + $"nextLeftSegmentId={nextLeftSegmentId}\n" + + "\t" + $"canCrossStreet={canCrossStreet}\n" + + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" + ); + } #endif - ProcessItemPedBicycle( + ProcessItemPedBicycle( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextLeftSegmentId, ref netManager.m_segments.m_buffer[nextLeftSegmentId], nextNodeId, ref nextNode, leftLaneIndex, leftLaneId, ref netManager.m_lanes.m_buffer[leftLaneId], connectOffset, connectOffset); - } + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextLeftSegmentId, ref netManager.m_segments.m_buffer[nextLeftSegmentId], nextNodeId, ref nextNode, leftLaneIndex, leftLaneId, ref netManager.m_lanes.m_buffer[leftLaneId], connectOffset, connectOffset); + } - if (rightLaneId != 0 && rightLaneId != leftLaneId && (nextRightSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { + if (rightLaneId != 0 && rightLaneId != leftLaneId && (nextRightSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> ped: Exploring right pedestrian lane\n" + - "\t" + $"leftLaneId={leftLaneId}\n" + - "\t" + $"rightLaneId={rightLaneId}\n" + - "\t" + $"nextRightSegmentId={nextRightSegmentId}\n" + - "\t" + $"canCrossStreet={canCrossStreet}\n" + - "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> ped: Exploring right pedestrian lane\n" + + "\t" + $"leftLaneId={leftLaneId}\n" + + "\t" + $"rightLaneId={rightLaneId}\n" + + "\t" + $"nextRightSegmentId={nextRightSegmentId}\n" + + "\t" + $"canCrossStreet={canCrossStreet}\n" + + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" + ); + } #endif - ProcessItemPedBicycle( + ProcessItemPedBicycle( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextRightSegmentId, ref netManager.m_segments.m_buffer[nextRightSegmentId], nextNodeId, ref nextNode, rightLaneIndex, rightLaneId, ref netManager.m_lanes.m_buffer[rightLaneId], connectOffset, connectOffset); - } + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextRightSegmentId, ref netManager.m_segments.m_buffer[nextRightSegmentId], nextNodeId, ref nextNode, rightLaneIndex, rightLaneId, ref netManager.m_lanes.m_buffer[rightLaneId], connectOffset, connectOffset); + } - // switch from bicycle lane to pedestrian lane - int nextLaneIndex; - uint nextLaneId; - if ((m_vehicleTypes & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None && - prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Bicycle, out nextLaneIndex, out nextLaneId)) { + // switch from bicycle lane to pedestrian lane + int nextLaneIndex; + uint nextLaneId; + if ((m_vehicleTypes & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None && + prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Bicycle, out nextLaneIndex, out nextLaneId)) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: bicycle -> ped: Exploring bicycle switch\n" + - "\t" + $"leftLaneId={leftLaneId}\n" + - "\t" + $"rightLaneId={rightLaneId}\n" + - "\t" + $"nextRightSegmentId={nextRightSegmentId}\n" + - "\t" + $"canCrossStreet={canCrossStreet}\n" + - "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: bicycle -> ped: Exploring bicycle switch\n" + + "\t" + $"leftLaneId={leftLaneId}\n" + + "\t" + $"rightLaneId={rightLaneId}\n" + + "\t" + $"nextRightSegmentId={nextRightSegmentId}\n" + + "\t" + $"canCrossStreet={canCrossStreet}\n" + + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" + ); + } #endif - ProcessItemPedBicycle( + ProcessItemPedBicycle( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, nextLaneIndex, nextLaneId, ref netManager.m_lanes.m_buffer[nextLaneId], connectOffset, connectOffset); - } - } else { + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, nextLaneIndex, nextLaneId, ref netManager.m_lanes.m_buffer[nextLaneId], connectOffset, connectOffset); + } + } else { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: beautification -> ped: Exploring pedestrian lane to beautficiation node\n" + - "\t" + $"nextNodeId={nextNodeId}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: beautification -> ped: Exploring pedestrian lane to beautficiation node\n" + + "\t" + $"nextNodeId={nextNodeId}" + ); + } #endif - // we are going from pedestrian lane to a beautification node - for (int j = 0; j < 8; j++) { - ushort nextSegmentId = nextNode.GetSegment(j); - if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { + // we are going from pedestrian lane to a beautification node + for (int j = 0; j < 8; j++) { + ushort nextSegmentId = nextNode.GetSegment(j); + if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: beautification -> ped: Exploring next segment behind beautification node\n" + - "\t" + $"nextSegmentId={nextSegmentId}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: beautification -> ped: Exploring next segment behind beautification node\n" + + "\t" + $"nextSegmentId={nextSegmentId}" + ); + } #endif - ProcessItemCosts( + ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, false, true); - } - } - } + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, false, true); + } + } + } - // prepare switching from a vehicle to pedestrian lane - NetInfo.LaneType nextLaneType = m_laneTypes & ~NetInfo.LaneType.Pedestrian; - VehicleInfo.VehicleType nextVehicleType = m_vehicleTypes & ~VehicleInfo.VehicleType.Bicycle; - if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - nextLaneType &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } + // prepare switching from a vehicle to pedestrian lane + NetInfo.LaneType nextLaneType = m_laneTypes & ~NetInfo.LaneType.Pedestrian; + VehicleInfo.VehicleType nextVehicleType = m_vehicleTypes & ~VehicleInfo.VehicleType.Bicycle; + if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + nextLaneType &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Prepared parameters\n" + - "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + - "\t" + $"nextVehicleType={nextVehicleType}\n" + - "\t" + $"nextLaneType={nextLaneType}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Prepared parameters\n" + + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + + "\t" + $"nextVehicleType={nextVehicleType}\n" + + "\t" + $"nextLaneType={nextLaneType}" + ); + } #endif - // NON-STOCK CODE START - bool parkingAllowed = true; + // NON-STOCK CODE START + bool parkingAllowed = true; #if PARKINGAI - // Parking AI: Determine if parking is allowed - if (Options.parkingAI) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Parking AI: Determining if parking is allowed here\n" + - "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + - "\t" + $"nextVehicleType={nextVehicleType}\n" + - "\t" + $"nextLaneType={nextLaneType}\n" + - "\t" + $"item.m_lanesUsed={item.m_lanesUsed}\n" + - "\t" + $"m_endLaneA={m_endLaneA}\n" + - "\t" + $"m_endLaneB={m_endLaneB}" - ); - } -#endif - - if (m_queueItem.vehicleType == ExtVehicleType.PassengerCar && - (nextVehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None && - ((nextLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None)) { - if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - /* if pocket cars are prohibited, a citizen may only park their car once per path */ - parkingAllowed = false; - } else if ((item.m_lanesUsed & NetInfo.LaneType.PublicTransport) == NetInfo.LaneType.None) { - /* if the citizen is walking to their target (= no public transport used), the passenger car must be parked in the very last moment */ - parkingAllowed = item.m_laneID == m_endLaneA || item.m_laneID == m_endLaneB; - } - } - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Parking AI: Parking allowed here? {parkingAllowed}"); - } -#endif - } -#endif - // NON-STOCK CODE END - - int sameSegLaneIndex; - uint sameSegLaneId; - if (parkingAllowed && // NON-STOCK CODE - nextLaneType != NetInfo.LaneType.None && - nextVehicleType != VehicleInfo.VehicleType.None && - prevSegment.GetClosestLane(prevLaneIndex, nextLaneType, nextVehicleType, out sameSegLaneIndex, out sameSegLaneId) - ) { - NetInfo.Lane sameSegLaneInfo = prevSegmentInfo.m_lanes[sameSegLaneIndex]; - byte sameSegConnectOffset = (byte)(((prevSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None == ((sameSegLaneInfo.m_finalDirection & NetInfo.Direction.Backward) != NetInfo.Direction.None)) ? 1 : 254); - BufferItem nextItem = item; - if (m_randomParking) { - nextItem.m_comparisonValue += (float)m_pathRandomizer.Int32(300u) / m_maxLength; - } - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Exploring parking\n" + - "\t" + $"nextLaneType={nextLaneType}\n" + - "\t" + $"nextVehicleType={nextVehicleType}\n" + - "\t" + $"nextLaneType={nextLaneType}\n" + - "\t" + $"sameSegConnectOffset={sameSegConnectOffset}\n" + - "\t" + $"m_randomParking={m_randomParking}" - ); - } -#endif - - ProcessItemPedBicycle( -#if DEBUG - debug, unitId, -#endif - nextItem, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, sameSegLaneIndex, sameSegLaneId, ref netManager.m_lanes.m_buffer[sameSegLaneId], sameSegConnectOffset, 128); - } - } - } else { - // We are going to a non-pedestrian lane - - bool nextIsBeautificationNode = nextNode.Info.m_class.m_service == ItemClass.Service.Beautification; // NON-STOCK CODE (refactored) - bool allowPedestrian = (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None; // allow switching from pedestrian lane to a non-pedestrian lane? - bool allowBicycle = false; // allow switching from a pedestrian lane to a bike lane? - byte switchConnectOffset = 0; // lane switching offset - if (allowPedestrian) { - if (prevIsBicycleLane) { - // we are going to a bicycle lane - switchConnectOffset = connectOffset; - allowBicycle = nextIsBeautificationNode; -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Switching to a bicycle may be allowed here\n" + - "\t" + $"switchConnectOffset={switchConnectOffset}\n" + - "\t" + $"allowBicycle={allowBicycle}" - ); - } -#endif - } else if (m_vehicleLane != 0) { - // there is a parked vehicle position - if (m_vehicleLane != item.m_laneID) { - // we have not reached the parked vehicle yet - allowPedestrian = false; -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a parked vehicle is not allowed here"); - } -#endif - } else { - // pedestrian switches to parked vehicle - switchConnectOffset = m_vehicleOffset; -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a parked vehicle is allowed here\n" + - "\t" + $"switchConnectOffset={switchConnectOffset}" - ); - } -#endif - } - } else if (m_stablePath) { - // enter a bus - switchConnectOffset = 128; -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a bus is allowed here\n" + - "\t" + $"switchConnectOffset={switchConnectOffset}" - ); - } -#endif - } else { - // pocket car spawning + // Parking AI: Determine if parking is allowed + if (Options.parkingAI) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Parking AI: Determining if parking is allowed here\n" + + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + + "\t" + $"nextVehicleType={nextVehicleType}\n" + + "\t" + $"nextLaneType={nextLaneType}\n" + + "\t" + $"item.m_lanesUsed={item.m_lanesUsed}\n" + + "\t" + $"m_endLaneA={m_endLaneA}\n" + + "\t" + $"m_endLaneB={m_endLaneB}" + ); + } +#endif + + if (m_queueItem.vehicleType == ExtVehicleType.PassengerCar && + (nextVehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None && + ((nextLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None)) { + if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + /* if pocket cars are prohibited, a citizen may only park their car once per path */ + parkingAllowed = false; + } else if ((item.m_lanesUsed & NetInfo.LaneType.PublicTransport) == NetInfo.LaneType.None) { + /* if the citizen is walking to their target (= no public transport used), the passenger car must be parked in the very last moment */ + parkingAllowed = item.m_laneID == m_endLaneA || item.m_laneID == m_endLaneB; + } + } + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Parking AI: Parking allowed here? {parkingAllowed}"); + } +#endif + } +#endif + // NON-STOCK CODE END + + int sameSegLaneIndex; + uint sameSegLaneId; + if (parkingAllowed && // NON-STOCK CODE + nextLaneType != NetInfo.LaneType.None && + nextVehicleType != VehicleInfo.VehicleType.None && + prevSegment.GetClosestLane(prevLaneIndex, nextLaneType, nextVehicleType, out sameSegLaneIndex, out sameSegLaneId) + ) { + NetInfo.Lane sameSegLaneInfo = prevSegmentInfo.m_lanes[sameSegLaneIndex]; + byte sameSegConnectOffset = (byte)(((prevSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None == ((sameSegLaneInfo.m_finalDirection & NetInfo.Direction.Backward) != NetInfo.Direction.None)) ? 1 : 254); + BufferItem nextItem = item; + if (m_randomParking) { + nextItem.m_comparisonValue += (float)m_pathRandomizer.Int32(300u) / m_maxLength; + } + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Exploring parking\n" + + "\t" + $"nextLaneType={nextLaneType}\n" + + "\t" + $"nextVehicleType={nextVehicleType}\n" + + "\t" + $"nextLaneType={nextLaneType}\n" + + "\t" + $"sameSegConnectOffset={sameSegConnectOffset}\n" + + "\t" + $"m_randomParking={m_randomParking}" + ); + } +#endif + + ProcessItemPedBicycle( +#if DEBUG + debug, unitId, +#endif + nextItem, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, sameSegLaneIndex, sameSegLaneId, ref netManager.m_lanes.m_buffer[sameSegLaneId], sameSegConnectOffset, 128); + } + } + } else { + // We are going to a non-pedestrian lane + + bool nextIsBeautificationNode = nextNode.Info.m_class.m_service == ItemClass.Service.Beautification; // NON-STOCK CODE (refactored) + bool allowPedestrian = (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None; // allow switching from pedestrian lane to a non-pedestrian lane? + bool allowBicycle = false; // allow switching from a pedestrian lane to a bike lane? + byte switchConnectOffset = 0; // lane switching offset + if (allowPedestrian) { + if (prevIsBicycleLane) { + // we are going to a bicycle lane + switchConnectOffset = connectOffset; + allowBicycle = nextIsBeautificationNode; +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Switching to a bicycle may be allowed here\n" + + "\t" + $"switchConnectOffset={switchConnectOffset}\n" + + "\t" + $"allowBicycle={allowBicycle}" + ); + } +#endif + } else if (m_vehicleLane != 0) { + // there is a parked vehicle position + if (m_vehicleLane != item.m_laneID) { + // we have not reached the parked vehicle yet + allowPedestrian = false; +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a parked vehicle is not allowed here"); + } +#endif + } else { + // pedestrian switches to parked vehicle + switchConnectOffset = m_vehicleOffset; +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a parked vehicle is allowed here\n" + + "\t" + $"switchConnectOffset={switchConnectOffset}" + ); + } +#endif + } + } else if (m_stablePath) { + // enter a bus + switchConnectOffset = 128; +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a bus is allowed here\n" + + "\t" + $"switchConnectOffset={switchConnectOffset}" + ); + } +#endif + } else { + // pocket car spawning #if PARKINGAI - if ( - Options.parkingAI - ) { + if ( + Options.parkingAI + ) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Determining if spawning pocket cars is allowed\n" + - "\t" + $"m_queueItem.pathType={m_queueItem.pathType}\n" + - "\t" + $"prevIsCarLane={prevIsCarLane}\n" + - "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + - "\t" + $"m_startSegmentA={m_startSegmentA}\n" + - "\t" + $"m_startSegmentB={m_startSegmentB}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Determining if spawning pocket cars is allowed\n" + + "\t" + $"m_queueItem.pathType={m_queueItem.pathType}\n" + + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + + "\t" + $"m_startSegmentA={m_startSegmentA}\n" + + "\t" + $"m_startSegmentB={m_startSegmentB}" + ); + } #endif - if ( - (m_queueItem.pathType == ExtPathType.WalkingOnly && prevIsCarLane) || - ( - m_queueItem.pathType == ExtPathType.DrivingOnly && - m_queueItem.vehicleType == ExtVehicleType.PassengerCar && - ((item.m_position.m_segment != m_startSegmentA && item.m_position.m_segment != m_startSegmentB) || !prevIsCarLane) - ) - ) { - /* allow pocket cars only if an instant driving path is required and we are at the start segment */ - /* disallow pocket cars on walking paths */ - allowPedestrian = false; + if ( + (m_queueItem.pathType == ExtPathType.WalkingOnly && prevIsCarLane) || + ( + m_queueItem.pathType == ExtPathType.DrivingOnly && + m_queueItem.vehicleType == ExtVehicleType.PassengerCar && + ((item.m_position.m_segment != m_startSegmentA && item.m_position.m_segment != m_startSegmentB) || !prevIsCarLane) + ) + ) { + /* allow pocket cars only if an instant driving path is required and we are at the start segment */ + /* disallow pocket cars on walking paths */ + allowPedestrian = false; #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Spawning pocket cars is not allowed here"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Spawning pocket cars is not allowed here"); + } #endif - } else { - switchConnectOffset = (byte)m_pathRandomizer.UInt32(1u, 254u); + } else { + switchConnectOffset = (byte)m_pathRandomizer.UInt32(1u, 254u); #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Spawning pocket cars is allowed here\n" + - "\t" + $"switchConnectOffset={switchConnectOffset}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Spawning pocket cars is allowed here\n" + + "\t" + $"switchConnectOffset={switchConnectOffset}" + ); + } #endif - } - } else { + } + } else { #endif - switchConnectOffset = (byte)m_pathRandomizer.UInt32(1u, 254u); + switchConnectOffset = (byte)m_pathRandomizer.UInt32(1u, 254u); #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Spawning pocket cars is allowed here\n" + - "\t" + $"switchConnectOffset={switchConnectOffset}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Spawning pocket cars is allowed here\n" + + "\t" + $"switchConnectOffset={switchConnectOffset}" + ); + } #endif #if PARKINGAI - } + } #endif - } - } + } + } - ushort nextSegmentId = 0; - if ((m_vehicleTypes & (VehicleInfo.VehicleType.Ferry + ushort nextSegmentId = 0; + if ((m_vehicleTypes & (VehicleInfo.VehicleType.Ferry #if !ROUTING | VehicleInfo.VehicleType.Monorail #endif - )) != VehicleInfo.VehicleType.None) { - // ferry (/ monorail) + )) != VehicleInfo.VehicleType.None) { + // ferry (/ monorail) #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry routes"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry routes"); + } #endif - bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; - for (int k = 0; k < 8; k++) { - nextSegmentId = nextNode.GetSegment(k); - if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { + bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; + for (int k = 0; k < 8; k++) { + nextSegmentId = nextNode.GetSegment(k); + if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry route\n" + - "\t" + $"nextSegmentId={nextSegmentId}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry route\n" + + "\t" + $"nextSegmentId={nextSegmentId}" + ); + } #endif - ProcessItemCosts( + ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle); - } - } + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle); + } + } - if (isUturnAllowedHere + if (isUturnAllowedHere #if !ROUTING && (m_vehicleTypes & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None #endif - ) { + ) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry u-turn"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry u-turn"); + } #endif - nextSegmentId = prevSegmentId; - ProcessItemCosts( + nextSegmentId = prevSegmentId; + ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, false); - } - } else { - // road vehicles / trams / trains / metros (/ monorails) / etc. + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, false); + } + } else { + // road vehicles / trams / trains / metros (/ monorails) / etc. #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring vehicle routes"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring vehicle routes"); + } #endif #if ROUTING - bool exploreUturn = false; + bool exploreUturn = false; #else bool exploreUturn = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; #endif #if ROUTING - bool prevIsRouted = false; - uint laneRoutingIndex = 0; - bool nextIsStartNode = nextNodeId == prevSegment.m_startNode; - if (nextIsStartNode || nextNodeId == prevSegment.m_endNode) { - laneRoutingIndex = m_routingManager.GetLaneEndRoutingIndex(item.m_laneID, nextIsStartNode); - prevIsRouted = m_routingManager.LaneEndBackwardRoutings[laneRoutingIndex].routed; + bool prevIsRouted = false; + uint laneRoutingIndex = 0; + bool nextIsStartNode = nextNodeId == prevSegment.m_startNode; + if (nextIsStartNode || nextNodeId == prevSegment.m_endNode) { + laneRoutingIndex = m_routingManager.GetLaneEndRoutingIndex(item.m_laneID, nextIsStartNode); + prevIsRouted = m_routingManager.LaneEndBackwardRoutings[laneRoutingIndex].routed; #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Is previous segment routed? {prevIsRouted}"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Is previous segment routed? {prevIsRouted}"); + } #endif - } + } - if (allowBicycle || !prevIsRouted) { - /* - * pedestrian to bicycle lane switch or no routing information available: - * if pedestrian lanes should be explored (allowBicycle == true): do this here - * if previous segment has custom routing (prevIsRouted == true): do NOT explore vehicle lanes here, else: vanilla exploration of vehicle lanes - */ + if (allowBicycle || !prevIsRouted) { + /* + * pedestrian to bicycle lane switch or no routing information available: + * if pedestrian lanes should be explored (allowBicycle == true): do this here + * if previous segment has custom routing (prevIsRouted == true): do NOT explore vehicle lanes here, else: vanilla exploration of vehicle lanes + */ #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing\n" - + "\t" + $"prevIsRouted={prevIsRouted}\n" - + "\t" + $"allowBicycle={allowBicycle}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing\n" + + "\t" + $"prevIsRouted={prevIsRouted}\n" + + "\t" + $"allowBicycle={allowBicycle}" + ); + } #endif - // NON-STOCK CODE END + // NON-STOCK CODE END #endif - nextSegmentId = prevSegment.GetRightSegment(nextNodeId); - for (int l = 0; l < 8; l++) { - if (nextSegmentId == 0) { - break; - } + nextSegmentId = prevSegment.GetRightSegment(nextNodeId); + for (int l = 0; l < 8; l++) { + if (nextSegmentId == 0) { + break; + } - if (nextSegmentId == prevSegmentId) { - break; - } + if (nextSegmentId == prevSegmentId) { + break; + } #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing: exploring next segment\n" - + "\t" + $"nextSegmentId={nextSegmentId}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing: exploring next segment\n" + + "\t" + $"nextSegmentId={nextSegmentId}" + ); + } #endif - if (ProcessItemCosts( + if (ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, #if ROUTING - !prevIsRouted // NON-STOCK CODE + !prevIsRouted // NON-STOCK CODE #else true #endif - , allowBicycle) - ) { - exploreUturn = true; // allow exceptional u-turns + , allowBicycle) + ) { + exploreUturn = true; // allow exceptional u-turns #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing: exceptional u-turn allowed\n" - + "\t" + $"nextSegmentId={nextSegmentId}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing: exceptional u-turn allowed\n" + + "\t" + $"nextSegmentId={nextSegmentId}" + ); + } #endif - } + } - nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); - } + nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); + } #if ROUTING - } // NON-STOCK CODE + } // NON-STOCK CODE #endif #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing\n" - + "\t" + $"Options.advancedAI={Options.advancedAI}\n" - + "\t" + $"prevIsRouted={prevIsRouted}\n" - + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" - + "\t" + $"prevIsCarLane={prevIsCarLane}\n" - + "\t" + $"m_stablePath={Options.advancedAI}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing\n" + + "\t" + $"Options.advancedAI={Options.advancedAI}\n" + + "\t" + $"prevIsRouted={prevIsRouted}\n" + + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + + "\t" + $"m_stablePath={Options.advancedAI}" + ); + } #endif - // NON-STOCK CODE START - float segmentSelectionCost = 1f; - float laneSelectionCost = 1f; - float laneChangingCost = 1f; - bool enableAdvancedAI = false; - // NON-STOCK CODE END + // NON-STOCK CODE START + float segmentSelectionCost = 1f; + float laneSelectionCost = 1f; + float laneChangingCost = 1f; + bool enableAdvancedAI = false; + // NON-STOCK CODE END #if ADVANCEDAI && ROUTING - /* - * ======================================================================================================= - * Calculate Advanced Vehicle AI cost factors - * ======================================================================================================= - */ - if ( - Options.advancedAI && - prevIsRouted && - m_isRoadVehicle && - prevIsCarLane - ) { - enableAdvancedAI = true; - if (!m_stablePath) { - CalculateAdvancedAiCostFactors( -#if DEBUG - debug, unitId, -#endif - ref item, ref prevSegment, ref prevLane, nextNodeId, ref nextNode, ref segmentSelectionCost, ref laneSelectionCost, ref laneChangingCost - ); - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing with activated Advanced Vehicle AI: Calculated cost factors\n" - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}\n" - + "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - } else { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing with activated Advanced Vehicle AI and stable path: Using default cost factors\n" - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}\n" - + "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - } - } + /* + * ======================================================================================================= + * Calculate Advanced Vehicle AI cost factors + * ======================================================================================================= + */ + if ( + Options.advancedAI && + prevIsRouted && + m_isRoadVehicle && + prevIsCarLane + ) { + enableAdvancedAI = true; + if (!m_stablePath) { + CalculateAdvancedAiCostFactors( +#if DEBUG + debug, unitId, +#endif + ref item, ref prevSegment, ref prevLane, nextNodeId, ref nextNode, ref segmentSelectionCost, ref laneSelectionCost, ref laneChangingCost + ); + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing with activated Advanced Vehicle AI: Calculated cost factors\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + } else { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing with activated Advanced Vehicle AI and stable path: Using default cost factors\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + } + } #endif #if ROUTING - if (prevIsRouted) { + if (prevIsRouted) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Exploring custom routes"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Exploring custom routes"); + } #endif - exploreUturn = false; // custom routing processes regular u-turns - if (ProcessItemRouted( + exploreUturn = false; // custom routing processes regular u-turns + if (ProcessItemRouted( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed #if ADVANCEDAI - , enableAdvancedAI, laneChangingCost, + , enableAdvancedAI, laneChangingCost, #endif - segmentSelectionCost, laneSelectionCost, nextNodeId, ref nextNode, false, m_routingManager.SegmentRoutings[prevSegmentId], m_routingManager.LaneEndBackwardRoutings[laneRoutingIndex], connectOffset, prevRelSimilarLaneIndex - )) { - exploreUturn = true; // allow exceptional u-turns - } - } else { + segmentSelectionCost, laneSelectionCost, nextNodeId, ref nextNode, false, m_routingManager.SegmentRoutings[prevSegmentId], m_routingManager.LaneEndBackwardRoutings[laneRoutingIndex], connectOffset, prevRelSimilarLaneIndex + )) { + exploreUturn = true; // allow exceptional u-turns + } + } else { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: No custom routing present"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: No custom routing present"); + } #endif - if (!exploreUturn) { - // no exceptional u-turns allowed: allow regular u-turns - exploreUturn = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; + if (!exploreUturn) { + // no exceptional u-turns allowed: allow regular u-turns + exploreUturn = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Allowing regular u-turns:\n" - + "\t" + $"exploreUturn={exploreUturn}\n" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Allowing regular u-turns:\n" + + "\t" + $"exploreUturn={exploreUturn}\n" + ); + } #endif - } - } + } + } #endif - if (exploreUturn && (m_vehicleTypes & VehicleInfo.VehicleType.Tram) == VehicleInfo.VehicleType.None) { + if (exploreUturn && (m_vehicleTypes & VehicleInfo.VehicleType.Tram) == VehicleInfo.VehicleType.None) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring stock u-turn\n" - + "\t" + $"exploreUturn={exploreUturn}\n" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring stock u-turn\n" + + "\t" + $"exploreUturn={exploreUturn}\n" + ); + } #endif - ProcessItemCosts( + ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI && ROUTING - false, 0f, + false, 0f, #endif - nextNodeId, ref nextNode, false, prevSegmentId, ref prevSegment, + nextNodeId, ref nextNode, false, prevSegmentId, ref prevSegment, #if ROUTING - segmentSelectionCost, laneSelectionCost, null, + segmentSelectionCost, laneSelectionCost, null, #endif - ref prevRelSimilarLaneIndex, connectOffset, true, false - ); - } - } + ref prevRelSimilarLaneIndex, connectOffset, true, false + ); + } + } - if (allowPedestrian) { - int nextLaneIndex; - uint nextLaneId; - if (prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Pedestrian, m_vehicleTypes, out nextLaneIndex, out nextLaneId)) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Exploring switch\n" - + "\t" + $"nextLaneIndex={nextLaneIndex}\n" - + "\t" + $"nextLaneId={nextLaneId}" - ); - } -#endif - - ProcessItemPedBicycle( -#if DEBUG - debug, unitId, -#endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, nextLaneIndex, nextLaneId, ref netManager.m_lanes.m_buffer[nextLaneId], switchConnectOffset, switchConnectOffset); - } - } - } - - if (nextNode.m_lane != 0) { - bool targetDisabled = (nextNode.m_flags & (NetNode.Flags.Disabled | NetNode.Flags.DisableOnlyMiddle)) == NetNode.Flags.Disabled; - ushort nextSegmentId = netManager.m_lanes.m_buffer[nextNode.m_lane].m_segment; - if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: transport -> *: Exploring special node\n" - + "\t" + $"nextSegmentId={nextSegmentId}\n" - + "\t" + $"nextNode.m_lane={nextNode.m_lane}\n" - + "\t" + $"targetDisabled={targetDisabled}\n" - + "\t" + $"nextNodeId={nextNodeId}" - ); - } -#endif - - ProcessItemPublicTransport( -#if DEBUG - debug, unitId, -#endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, targetDisabled, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], nextNode.m_lane, nextNode.m_laneOffset, connectOffset); - } - } - } - - // 2 - private void ProcessItemPublicTransport( -#if DEBUG - bool debug, uint unitId, -#endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextNodeId, bool targetDisabled, ushort nextSegmentId, ref NetSegment nextSegment, uint nextLaneId, byte offset, byte connectOffset) { - -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport called.\n" - + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" - + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" - + "\t" + $"nextNodeId={nextNodeId}\n" - + "\t" + $"targetDisabled={targetDisabled}\n" - + "\t" + $"nextLaneId={nextLaneId}\n" - + "\t" + $"offset={offset}\n" - + "\t" + $"connectOffset={connectOffset}" - ); - } -#endif - - if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Disable mask\n" - + "\t" + $"m_disableMask={m_disableMask}\n" - + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); - } -#endif - return; - } - - NetManager netManager = Singleton.instance; - if (targetDisabled && ((netManager.m_nodes.m_buffer[nextSegment.m_startNode].m_flags | netManager.m_nodes.m_buffer[nextSegment.m_endNode].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Target disabled"); - } -#endif - return; - } - - NetInfo nextSegmentInfo = nextSegment.Info; - NetInfo prevSegmentInfo = prevSegment.Info; - int nextNumLanes = nextSegmentInfo.m_lanes.Length; - // float prevMaxSpeed = 1f; // stock code commented - // float prevLaneSpeed = 1f; // stock code commented - NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; - if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { - NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; - // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented - // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented - prevLaneType = prevLaneInfo.m_laneType; - if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - } - - float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; - float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; - float methodDistance = item.m_methodDistance + offsetLength; - float comparisonValue = item.m_comparisonValue + offsetLength / (prevLaneSpeed * m_maxLength); - float duration = item.m_duration + offsetLength / prevMaxSpeed; - Vector3 b = prevLane.CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - - if (!m_ignoreCost) { - int ticketCost = prevLane.m_ticketCost; - if (ticketCost != 0) { - comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; - } - } - - int nextLaneIndex = 0; - uint curLaneId = nextSegment.m_lanes; - while (true) { - if (nextLaneIndex < nextNumLanes && curLaneId != 0) { - if (nextLaneId != curLaneId) { - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - nextLaneIndex++; - continue; - } - break; - } -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Next lane not found"); - } -#endif - return; - } - -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Exploring next lane\n" - + "\t" + $"nextLaneIndex={nextLaneIndex}\n" - + "\t" + $"nextLaneId={nextLaneId}" - ); - } -#endif - - NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; - if (nextLaneInfo.CheckType(m_laneTypes, m_vehicleTypes)) { - -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Next lane compatible\n" - + "\t" + $"nextLaneInfo.m_vehicleType={nextLaneInfo.m_vehicleType}\n" - + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}" - ); - } -#endif - - Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)(int)offset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - float distance = Vector3.Distance(a, b); - BufferItem nextItem = default(BufferItem); - - nextItem.m_position.m_segment = nextSegmentId; - nextItem.m_position.m_lane = (byte)nextLaneIndex; - nextItem.m_position.m_offset = offset; - - if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { - nextItem.m_methodDistance = 0f; - } else { - nextItem.m_methodDistance = methodDistance + distance; - } - - if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Max. walking distance exceeded\n" - + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}" - ); - } -#endif - return; - } - - float nextMaxSpeed; + if (allowPedestrian) { + int nextLaneIndex; + uint nextLaneId; + if (prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Pedestrian, m_vehicleTypes, out nextLaneIndex, out nextLaneId)) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Exploring switch\n" + + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + + "\t" + $"nextLaneId={nextLaneId}" + ); + } +#endif + + ProcessItemPedBicycle( +#if DEBUG + debug, unitId, +#endif + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, nextLaneIndex, nextLaneId, ref netManager.m_lanes.m_buffer[nextLaneId], switchConnectOffset, switchConnectOffset); + } + } + } + + if (nextNode.m_lane != 0) { + bool targetDisabled = (nextNode.m_flags & (NetNode.Flags.Disabled | NetNode.Flags.DisableOnlyMiddle)) == NetNode.Flags.Disabled; + ushort nextSegmentId = netManager.m_lanes.m_buffer[nextNode.m_lane].m_segment; + if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemMain: transport -> *: Exploring special node\n" + + "\t" + $"nextSegmentId={nextSegmentId}\n" + + "\t" + $"nextNode.m_lane={nextNode.m_lane}\n" + + "\t" + $"targetDisabled={targetDisabled}\n" + + "\t" + $"nextNodeId={nextNodeId}" + ); + } +#endif + + ProcessItemPublicTransport( +#if DEBUG + debug, unitId, +#endif + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, targetDisabled, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], nextNode.m_lane, nextNode.m_laneOffset, connectOffset); + } + } + } + + // 2 + private void ProcessItemPublicTransport( +#if DEBUG + bool debug, uint unitId, +#endif + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextNodeId, bool targetDisabled, ushort nextSegmentId, ref NetSegment nextSegment, uint nextLaneId, byte offset, byte connectOffset) { + +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport called.\n" + + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" + + "\t" + $"nextNodeId={nextNodeId}\n" + + "\t" + $"targetDisabled={targetDisabled}\n" + + "\t" + $"nextLaneId={nextLaneId}\n" + + "\t" + $"offset={offset}\n" + + "\t" + $"connectOffset={connectOffset}" + ); + } +#endif + + if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Disable mask\n" + + "\t" + $"m_disableMask={m_disableMask}\n" + + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); + } +#endif + return; + } + + NetManager netManager = Singleton.instance; + if (targetDisabled && ((netManager.m_nodes.m_buffer[nextSegment.m_startNode].m_flags | netManager.m_nodes.m_buffer[nextSegment.m_endNode].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Target disabled"); + } +#endif + return; + } + + NetInfo nextSegmentInfo = nextSegment.Info; + NetInfo prevSegmentInfo = prevSegment.Info; + int nextNumLanes = nextSegmentInfo.m_lanes.Length; + // float prevMaxSpeed = 1f; // stock code commented + // float prevLaneSpeed = 1f; // stock code commented + NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; + if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { + NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; + // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented + // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented + prevLaneType = prevLaneInfo.m_laneType; + if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + } + + float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; + float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; + float methodDistance = item.m_methodDistance + offsetLength; + float comparisonValue = item.m_comparisonValue + offsetLength / (prevLaneSpeed * m_maxLength); + float duration = item.m_duration + offsetLength / prevMaxSpeed; + Vector3 b = prevLane.CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + + if (!m_ignoreCost) { + int ticketCost = prevLane.m_ticketCost; + if (ticketCost != 0) { + comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; + } + } + + int nextLaneIndex = 0; + uint curLaneId = nextSegment.m_lanes; + while (true) { + if (nextLaneIndex < nextNumLanes && curLaneId != 0) { + if (nextLaneId != curLaneId) { + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + nextLaneIndex++; + continue; + } + break; + } +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Next lane not found"); + } +#endif + return; + } + +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Exploring next lane\n" + + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + + "\t" + $"nextLaneId={nextLaneId}" + ); + } +#endif + + NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; + if (nextLaneInfo.CheckType(m_laneTypes, m_vehicleTypes)) { + +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Next lane compatible\n" + + "\t" + $"nextLaneInfo.m_vehicleType={nextLaneInfo.m_vehicleType}\n" + + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}" + ); + } +#endif + + Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)(int)offset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + float distance = Vector3.Distance(a, b); + BufferItem nextItem = default(BufferItem); + + nextItem.m_position.m_segment = nextSegmentId; + nextItem.m_position.m_lane = (byte)nextLaneIndex; + nextItem.m_position.m_offset = offset; + + if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { + nextItem.m_methodDistance = 0f; + } else { + nextItem.m_methodDistance = methodDistance + distance; + } + + if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Max. walking distance exceeded\n" + + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}" + ); + } +#endif + return; + } + + float nextMaxSpeed; #if SPEEDLIMITS - // NON-STOCK CODE START - nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); - // NON-STOCK CODE END + // NON-STOCK CODE START + nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); + // NON-STOCK CODE END #else nextMaxSpeed = nextLaneInfo.m_speedLimit; #endif - nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); - nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); + nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); + nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); - if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { - nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); - } else { - nextItem.m_direction = nextLaneInfo.m_finalDirection; - } + if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { + nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); + } else { + nextItem.m_direction = nextLaneInfo.m_finalDirection; + } - if (nextLaneId == m_startLaneA) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { + if (nextLaneId == m_startLaneA) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Invalid offset/direction on start lane A\n" - + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" - + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" - + "\t" + $"m_startOffsetA={m_startOffsetA}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Invalid offset/direction on start lane A\n" + + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + + "\t" + $"m_startOffsetA={m_startOffsetA}" + ); + } #endif - return; - } + return; + } - float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); - float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); + float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; - } + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; + } - if (nextLaneId == m_startLaneB) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { + if (nextLaneId == m_startLaneB) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Invalid offset/direction on start lane B\n" - + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" - + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" - + "\t" + $"m_startOffsetB={m_startOffsetB}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Invalid offset/direction on start lane B\n" + + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + + "\t" + $"m_startOffsetB={m_startOffsetB}" + ); + } #endif - return; - } + return; + } - float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); - float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); + float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; - } + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; + } - nextItem.m_laneID = nextLaneId; - nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); + nextItem.m_laneID = nextLaneId; + nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); #if PARKINGAI - nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); + nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); #endif #if ADVANCEDAI && ROUTING - // NON-STOCK CODE START - nextItem.m_trafficRand = item.m_trafficRand; - // NON-STOCK CODE END + // NON-STOCK CODE START + nextItem.m_trafficRand = item.m_trafficRand; + // NON-STOCK CODE END #endif #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Adding next item\n" - + "\t" + $"nextItem={nextItem}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Adding next item\n" + + "\t" + $"nextItem={nextItem}" + ); + } #endif - AddBufferItem( + AddBufferItem( #if DEBUG - debug, + debug, #endif - nextItem, item.m_position - ); - } - } + nextItem, item.m_position + ); + } + } #if ADVANCEDAI && ROUTING - // 3a (non-routed, no adv. AI) - private bool ProcessItemCosts( + // 3a (non-routed, no adv. AI) + private bool ProcessItemCosts( #if DEBUG - bool debug, uint unitId, + bool debug, uint unitId, #endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian) { - return ProcessItemCosts( + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian) { + return ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI && ROUTING - false, 0f, + false, 0f, #endif - nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref nextSegment, + nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref nextSegment, #if ROUTING - 1f, 1f, null, + 1f, 1f, null, #endif - ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian); - } + ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian); + } #endif - // 3b - private bool ProcessItemCosts( + // 3b + private bool ProcessItemCosts( #if DEBUG - bool debug, uint unitId, + bool debug, uint unitId, #endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, #if ADVANCEDAI && ROUTING - bool enableAdvancedAI, float laneChangingCost, + bool enableAdvancedAI, float laneChangingCost, #endif - ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, + ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, #if ROUTING - float segmentSelectionCost, float laneSelectionCost, LaneTransitionData? transition, + float segmentSelectionCost, float laneSelectionCost, LaneTransitionData? transition, #endif - ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian - ) { + ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian + ) { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts called.\n" - + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" - + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts called.\n" + + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" #if ADVANCEDAI && ROUTING - + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" - + "\t" + $"laneChangingCost={laneChangingCost}\n" + + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + + "\t" + $"laneChangingCost={laneChangingCost}\n" #endif - + "\t" + $"nextNodeId={nextNodeId}\n" - + "\t" + $"isMiddle={isMiddle}\n" - + "\t" + $"nextSegmentId={nextSegmentId}\n" + + "\t" + $"nextNodeId={nextNodeId}\n" + + "\t" + $"isMiddle={isMiddle}\n" + + "\t" + $"nextSegmentId={nextSegmentId}\n" #if ROUTING - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}\n" - + "\t" + $"transition={transition}\n" -#endif - + "\t" + $"laneIndexFromInner={laneIndexFromInner}\n" - + "\t" + $"connectOffset={connectOffset}\n" - + "\t" + $"enableVehicle={enableVehicle}\n" - + "\t" + $"enablePedestrian={enablePedestrian}" - ); - } -#endif - - bool blocked = false; - if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Aborting: Disable mask\n" - + "\t" + $"m_disableMask={m_disableMask}\n" - + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); - } -#endif - return blocked; - } - - NetManager netManager = Singleton.instance; - NetInfo nextSegmentInfo = nextSegment.Info; - NetInfo prevSegmentInfo = prevSegment.Info; - int nextNumLanes = nextSegmentInfo.m_lanes.Length; - NetInfo.Direction nextDir = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; - NetInfo.Direction nextFinalDir = ((nextSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? nextDir : NetInfo.InvertDirection(nextDir); - // float prevMaxSpeed = 1f; // stock code commented - // float prevLaneSpeed = 1f; // stock code commented - NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; - VehicleInfo.VehicleType prevVehicleType = VehicleInfo.VehicleType.None; - if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { - NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; - prevLaneType = prevLaneInfo.m_laneType; - prevVehicleType = prevLaneInfo.m_vehicleType; - // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented - // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented - } - - bool acuteTurningAngle = false; - if (prevLaneType == NetInfo.LaneType.Vehicle && (prevVehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { - float turningAngle = 0.01f - Mathf.Min(nextSegmentInfo.m_maxTurnAngleCos, prevSegmentInfo.m_maxTurnAngleCos); - if (turningAngle < 1f) { - Vector3 vector = (nextNodeId != prevSegment.m_startNode) ? prevSegment.m_endDirection : prevSegment.m_startDirection; - Vector3 vector2 = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? nextSegment.m_startDirection : nextSegment.m_endDirection; - float dirDotProd = vector.x * vector2.x + vector.z * vector2.z; - if (dirDotProd >= turningAngle) { - acuteTurningAngle = true; - } - } - } - - float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; - float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; - float methodDistance = item.m_methodDistance + offsetLength; - float duration = item.m_duration + offsetLength / prevMaxSpeed; - - if (!m_stablePath) { + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"transition={transition}\n" +#endif + + "\t" + $"laneIndexFromInner={laneIndexFromInner}\n" + + "\t" + $"connectOffset={connectOffset}\n" + + "\t" + $"enableVehicle={enableVehicle}\n" + + "\t" + $"enablePedestrian={enablePedestrian}" + ); + } +#endif + + bool blocked = false; + if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Aborting: Disable mask\n" + + "\t" + $"m_disableMask={m_disableMask}\n" + + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); + } +#endif + return blocked; + } + + NetManager netManager = Singleton.instance; + NetInfo nextSegmentInfo = nextSegment.Info; + NetInfo prevSegmentInfo = prevSegment.Info; + int nextNumLanes = nextSegmentInfo.m_lanes.Length; + NetInfo.Direction nextDir = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; + NetInfo.Direction nextFinalDir = ((nextSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? nextDir : NetInfo.InvertDirection(nextDir); + // float prevMaxSpeed = 1f; // stock code commented + // float prevLaneSpeed = 1f; // stock code commented + NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; + VehicleInfo.VehicleType prevVehicleType = VehicleInfo.VehicleType.None; + if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { + NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; + prevLaneType = prevLaneInfo.m_laneType; + prevVehicleType = prevLaneInfo.m_vehicleType; + // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented + // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented + } + + bool acuteTurningAngle = false; + if (prevLaneType == NetInfo.LaneType.Vehicle && (prevVehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { + float turningAngle = 0.01f - Mathf.Min(nextSegmentInfo.m_maxTurnAngleCos, prevSegmentInfo.m_maxTurnAngleCos); + if (turningAngle < 1f) { + Vector3 vector = (nextNodeId != prevSegment.m_startNode) ? prevSegment.m_endDirection : prevSegment.m_startDirection; + Vector3 vector2 = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? nextSegment.m_startDirection : nextSegment.m_endDirection; + float dirDotProd = vector.x * vector2.x + vector.z * vector2.z; + if (dirDotProd >= turningAngle) { + acuteTurningAngle = true; + } + } + } + + float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; + float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; + float methodDistance = item.m_methodDistance + offsetLength; + float duration = item.m_duration + offsetLength / prevMaxSpeed; + + if (!m_stablePath) { #if ADVANCEDAI && ROUTING - if (!enableAdvancedAI) { + if (!enableAdvancedAI) { #endif - offsetLength *= (float)(new Randomizer(m_pathFindIndex << 16 | item.m_position.m_segment).Int32(900, 1000 + prevSegment.m_trafficDensity * 10) + m_pathRandomizer.Int32(20u)) * 0.001f; + offsetLength *= (float)(new Randomizer(m_pathFindIndex << 16 | item.m_position.m_segment).Int32(900, 1000 + prevSegment.m_trafficDensity * 10) + m_pathRandomizer.Int32(20u)) * 0.001f; #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock segment randomization cost factor\n" - + "\t" + $"offsetLength={offsetLength}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock segment randomization cost factor\n" + + "\t" + $"offsetLength={offsetLength}" + ); + } #endif #if ADVANCEDAI && ROUTING - } + } #endif - } + } - if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevVehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Car && (prevSegment.m_flags & m_carBanMask) != NetSegment.Flags.None) { - offsetLength *= 7.5f; + if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevVehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Car && (prevSegment.m_flags & m_carBanMask) != NetSegment.Flags.None) { + offsetLength *= 7.5f; #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock car ban cost factor\n" - + "\t" + $"offsetLength={offsetLength}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock car ban cost factor\n" + + "\t" + $"offsetLength={offsetLength}" + ); + } #endif - } + } - if (m_transportVehicle && prevLaneType == NetInfo.LaneType.TransportVehicle) { - offsetLength *= 0.95f; + if (m_transportVehicle && prevLaneType == NetInfo.LaneType.TransportVehicle) { + offsetLength *= 0.95f; #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock transport vehicle cost factor\n" - + "\t" + $"offsetLength={offsetLength}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock transport vehicle cost factor\n" + + "\t" + $"offsetLength={offsetLength}" + ); + } #endif - } + } #if ROUTING #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applying custom selection cost factors\n" - + "\t" + $"offsetLength={offsetLength}\n" - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}\n" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applying custom selection cost factors\n" + + "\t" + $"offsetLength={offsetLength}\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + ); + } #endif - offsetLength *= segmentSelectionCost; - offsetLength *= laneSelectionCost; + offsetLength *= segmentSelectionCost; + offsetLength *= laneSelectionCost; #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied custom selection cost factors\n" - + "\t" + $"offsetLength={offsetLength}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied custom selection cost factors\n" + + "\t" + $"offsetLength={offsetLength}" + ); + } #endif #endif - float baseLength = offsetLength / (prevLaneSpeed * m_maxLength); // NON-STOCK CODE - float comparisonValue = item.m_comparisonValue; // NON-STOCK CODE + float baseLength = offsetLength / (prevLaneSpeed * m_maxLength); // NON-STOCK CODE + float comparisonValue = item.m_comparisonValue; // NON-STOCK CODE #if ROUTING #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Calculated base length\n" - + "\t" + $"baseLength={baseLength}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Calculated base length\n" + + "\t" + $"baseLength={baseLength}" + ); + } #endif - if ( + if ( #if ADVANCEDAI - !enableAdvancedAI && -#endif - !m_stablePath) { - comparisonValue += baseLength; - } -#endif - int ticketCost = prevLane.m_ticketCost; - if (!m_ignoreCost && ticketCost != 0) { - comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; - } - - if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - - Vector3 b = prevLane.CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - int newLaneIndexFromInner = laneIndexFromInner; - bool isTransition = (nextNode.m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; - - NetInfo.LaneType allowedLaneTypes = m_laneTypes; - VehicleInfo.VehicleType allowedVehicleTypes = m_vehicleTypes; - if (!enableVehicle) { - allowedVehicleTypes &= VehicleInfo.VehicleType.Bicycle; - if (allowedVehicleTypes == VehicleInfo.VehicleType.None) { - allowedLaneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - } - if (!enablePedestrian) { - allowedLaneTypes &= ~NetInfo.LaneType.Pedestrian; - } - - // NON-STOCK CODE START - bool applyTransportTransferPenalty = - Options.realisticPublicTransport && - !m_stablePath && - (allowedLaneTypes & (NetInfo.LaneType.PublicTransport | NetInfo.LaneType.Pedestrian)) == (NetInfo.LaneType.PublicTransport | NetInfo.LaneType.Pedestrian) && - m_conf.PathFinding.PublicTransportTransitionMinPenalty >= 0 && - m_conf.PathFinding.PublicTransportTransitionMaxPenalty > m_conf.PathFinding.PublicTransportTransitionMinPenalty - ; - -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Shall apply transport transfer penalty?\n" - + "\t" + $"applyTransportTransferPenalty={applyTransportTransferPenalty}\n" - + "\t" + $"Options.realisticPublicTransport={Options.realisticPublicTransport}\n" - + "\t" + $"allowedLaneTypes={allowedLaneTypes}\n" - + "\t" + $"allowedVehicleTypes={allowedVehicleTypes}\n" - + "\t" + $"m_conf.PathFinding.PublicTransportTransitionMinPenalty={m_conf.PathFinding.PublicTransportTransitionMinPenalty}\n" - + "\t" + $"m_conf.PathFinding.PublicTransportTransitionMaxPenalty={m_conf.PathFinding.PublicTransportTransitionMaxPenalty}" - ); - } -#endif - - int nextLaneIndex = 0; - uint nextLaneId = nextSegment.m_lanes; - int maxNextLaneIndex = nextNumLanes - 1; + !enableAdvancedAI && +#endif + !m_stablePath) { + comparisonValue += baseLength; + } +#endif + int ticketCost = prevLane.m_ticketCost; + if (!m_ignoreCost && ticketCost != 0) { + comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; + } + + if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + + Vector3 b = prevLane.CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + int newLaneIndexFromInner = laneIndexFromInner; + bool isTransition = (nextNode.m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; + + NetInfo.LaneType allowedLaneTypes = m_laneTypes; + VehicleInfo.VehicleType allowedVehicleTypes = m_vehicleTypes; + if (!enableVehicle) { + allowedVehicleTypes &= VehicleInfo.VehicleType.Bicycle; + if (allowedVehicleTypes == VehicleInfo.VehicleType.None) { + allowedLaneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + } + if (!enablePedestrian) { + allowedLaneTypes &= ~NetInfo.LaneType.Pedestrian; + } + + // NON-STOCK CODE START + bool applyTransportTransferPenalty = + Options.realisticPublicTransport && + !m_stablePath && + (allowedLaneTypes & (NetInfo.LaneType.PublicTransport | NetInfo.LaneType.Pedestrian)) == (NetInfo.LaneType.PublicTransport | NetInfo.LaneType.Pedestrian) && + m_conf.PathFinding.PublicTransportTransitionMinPenalty >= 0 && + m_conf.PathFinding.PublicTransportTransitionMaxPenalty > m_conf.PathFinding.PublicTransportTransitionMinPenalty + ; + +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Shall apply transport transfer penalty?\n" + + "\t" + $"applyTransportTransferPenalty={applyTransportTransferPenalty}\n" + + "\t" + $"Options.realisticPublicTransport={Options.realisticPublicTransport}\n" + + "\t" + $"allowedLaneTypes={allowedLaneTypes}\n" + + "\t" + $"allowedVehicleTypes={allowedVehicleTypes}\n" + + "\t" + $"m_conf.PathFinding.PublicTransportTransitionMinPenalty={m_conf.PathFinding.PublicTransportTransitionMinPenalty}\n" + + "\t" + $"m_conf.PathFinding.PublicTransportTransitionMaxPenalty={m_conf.PathFinding.PublicTransportTransitionMaxPenalty}" + ); + } +#endif + + int nextLaneIndex = 0; + uint nextLaneId = nextSegment.m_lanes; + int maxNextLaneIndex = nextNumLanes - 1; #if ADVANCEDAI && ROUTING - byte laneDist = 0; + byte laneDist = 0; #endif #if ROUTING - if (transition != null) { - LaneTransitionData trans = (LaneTransitionData)transition; - if (trans.laneIndex >= 0 && trans.laneIndex <= maxNextLaneIndex) { - nextLaneIndex = trans.laneIndex; - nextLaneId = trans.laneId; - maxNextLaneIndex = nextLaneIndex; - } else { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Invalid transition detected. Skipping."); - } -#endif - return blocked; - } - - laneDist = trans.distance; -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Custom transition given\n" - + "\t" + $"nextLaneIndex={nextLaneIndex}\n" - + "\t" + $"nextLaneId={nextLaneId}\n" - + "\t" + $"maxNextLaneIndex={maxNextLaneIndex}\n" - + "\t" + $"laneDist={laneDist}" - ); - } -#endif - } else { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: No custom transition given"); - } -#endif - } -#endif - // NON-STOCK CODE END - - for (; nextLaneIndex <= maxNextLaneIndex && nextLaneId != 0; nextLaneIndex++) { - NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; - if ((nextLaneInfo.m_finalDirection & nextFinalDir) != NetInfo.Direction.None) { - if (nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes) && (nextSegmentId != item.m_position.m_segment || nextLaneIndex != item.m_position.m_lane)) { - if (acuteTurningAngle && nextLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (nextLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { - continue; - } - - BufferItem nextItem = default(BufferItem); - - Vector3 a = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? netManager.m_lanes.m_buffer[nextLaneId].m_bezier.a : netManager.m_lanes.m_buffer[nextLaneId].m_bezier.d; - float transitionCost = Vector3.Distance(a, b); - if (isTransition) { - transitionCost *= 2f; - } - if (ticketCost != 0 && netManager.m_lanes.m_buffer[nextLaneId].m_ticketCost != 0) { - transitionCost *= 10f; - } - - float nextMaxSpeed; + if (transition != null) { + LaneTransitionData trans = (LaneTransitionData)transition; + if (trans.laneIndex >= 0 && trans.laneIndex <= maxNextLaneIndex) { + nextLaneIndex = trans.laneIndex; + nextLaneId = trans.laneId; + maxNextLaneIndex = nextLaneIndex; + } else { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Invalid transition detected. Skipping."); + } +#endif + return blocked; + } + + laneDist = trans.distance; +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Custom transition given\n" + + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + + "\t" + $"nextLaneId={nextLaneId}\n" + + "\t" + $"maxNextLaneIndex={maxNextLaneIndex}\n" + + "\t" + $"laneDist={laneDist}" + ); + } +#endif + } else { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: No custom transition given"); + } +#endif + } +#endif + // NON-STOCK CODE END + + for (; nextLaneIndex <= maxNextLaneIndex && nextLaneId != 0; nextLaneIndex++) { + NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; + if ((nextLaneInfo.m_finalDirection & nextFinalDir) != NetInfo.Direction.None) { + if (nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes) && (nextSegmentId != item.m_position.m_segment || nextLaneIndex != item.m_position.m_lane)) { + if (acuteTurningAngle && nextLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (nextLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { + continue; + } + + BufferItem nextItem = default(BufferItem); + + Vector3 a = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? netManager.m_lanes.m_buffer[nextLaneId].m_bezier.a : netManager.m_lanes.m_buffer[nextLaneId].m_bezier.d; + float transitionCost = Vector3.Distance(a, b); + if (isTransition) { + transitionCost *= 2f; + } + if (ticketCost != 0 && netManager.m_lanes.m_buffer[nextLaneId].m_ticketCost != 0) { + transitionCost *= 10f; + } + + float nextMaxSpeed; #if SPEEDLIMITS - // NON-STOCK CODE START - nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); - // NON-STOCK CODE END + // NON-STOCK CODE START + nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); + // NON-STOCK CODE END #else nextMaxSpeed = nextLaneInfo.m_speedLimit; #endif - float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); + float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); #if ADVANCEDAI && ROUTING - if (!enableAdvancedAI) { + if (!enableAdvancedAI) { #endif - if (!this.m_stablePath && (netManager.m_lanes.m_buffer[nextLaneId].m_flags & (ushort)NetLane.Flags.Merge) != 0) { - int firstTarget = netManager.m_lanes.m_buffer[nextLaneId].m_firstTarget; - int lastTarget = netManager.m_lanes.m_buffer[nextLaneId].m_lastTarget; - transitionCostOverMeanMaxSpeed *= (float)new Randomizer(this.m_pathFindIndex ^ nextLaneId).Int32(1000, (lastTarget - firstTarget + 2) * 1000) * 0.001f; - } + if (!this.m_stablePath && (netManager.m_lanes.m_buffer[nextLaneId].m_flags & (ushort)NetLane.Flags.Merge) != 0) { + int firstTarget = netManager.m_lanes.m_buffer[nextLaneId].m_firstTarget; + int lastTarget = netManager.m_lanes.m_buffer[nextLaneId].m_lastTarget; + transitionCostOverMeanMaxSpeed *= (float)new Randomizer(this.m_pathFindIndex ^ nextLaneId).Int32(1000, (lastTarget - firstTarget + 2) * 1000) * 0.001f; + } #if ADVANCEDAI && ROUTING - } -#endif - nextItem.m_position.m_segment = nextSegmentId; - nextItem.m_position.m_lane = (byte)nextLaneIndex; - nextItem.m_position.m_offset = (byte)(((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) ? 255 : 0); - if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { - nextItem.m_methodDistance = 0f; - } else { - nextItem.m_methodDistance = methodDistance + transitionCost; - } - - if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) - nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; - continue; - } - - // NON-STOCK CODE START - if (applyTransportTransferPenalty) { - if ( - isMiddle && - (nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None && - (item.m_lanesUsed & NetInfo.LaneType.PublicTransport) != NetInfo.LaneType.None && - nextLaneInfo.m_laneType == NetInfo.LaneType.PublicTransport - ) { - // apply penalty when switching between public transport lines - float transportTransitionPenalty = (m_conf.PathFinding.PublicTransportTransitionMinPenalty + ((float)nextNode.m_maxWaitTime * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR) * (m_conf.PathFinding.PublicTransportTransitionMaxPenalty - m_conf.PathFinding.PublicTransportTransitionMinPenalty)) / (0.5f * this.m_maxLength); - transitionCostOverMeanMaxSpeed += transportTransitionPenalty; - -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied transport transfer penalty on PT change\n" - + "\t" + $"transportTransitionPenalty={transportTransitionPenalty}\n" - + "\t" + $"transitionCostOverMeanMaxSpeed={transitionCostOverMeanMaxSpeed}\n" - + "\t" + $"isMiddle={isMiddle}\n" - + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}\n" - + "\t" + $"prevLaneType={prevLaneType}\n" - + "\t" + $"item.m_lanesUsed={item.m_lanesUsed}\n" - + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}" - ); - } -#endif - } else if ( - (nextLaneId == m_startLaneA || nextLaneId == m_startLaneB) && - (item.m_lanesUsed & (NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport)) == NetInfo.LaneType.Pedestrian - ) { - // account for public tranport transition costs on non-PT paths - float transportTransitionPenalty = (2f * m_conf.PathFinding.PublicTransportTransitionMaxPenalty) / (0.5f * this.m_maxLength); - transitionCostOverMeanMaxSpeed += transportTransitionPenalty; -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied transport transfer penalty on non-PT path\n" - + "\t" + $"transportTransitionPenalty={transportTransitionPenalty}\n" - + "\t" + $"transitionCostOverMeanMaxSpeed={transitionCostOverMeanMaxSpeed}" - ); - } -#endif - } - } - // NON-STOCK CODE END - - nextItem.m_comparisonValue = comparisonValue + transitionCostOverMeanMaxSpeed; - nextItem.m_duration = duration + transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); - nextItem.m_direction = nextDir; - - if (nextLaneId == m_startLaneA) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Skipping: Invalid offset/direction on start lane A\n" - + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" - + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" - + "\t" + $"m_startOffsetA={m_startOffsetA}" - ); - } -#endif - - nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; - continue; - } - - float nextLaneSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); - float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextLaneSpeed * m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextLaneSpeed; - } - - if (nextLaneId == m_startLaneB) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Skipping: Invalid offset/direction on start lane B\n" - + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" - + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" - + "\t" + $"m_startOffsetB={m_startOffsetB}" - ); - } -#endif - - nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; - continue; - } - - float nextLaneSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); - float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextLaneSpeed * m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextLaneSpeed; - } - - if ( - !m_ignoreBlocked && (nextSegment.m_flags & NetSegment.Flags.Blocked) != NetSegment.Flags.None && - (nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None - ) { - nextItem.m_comparisonValue += 0.1f; - blocked = true; - } - - nextItem.m_laneID = nextLaneId; - nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); + } +#endif + nextItem.m_position.m_segment = nextSegmentId; + nextItem.m_position.m_lane = (byte)nextLaneIndex; + nextItem.m_position.m_offset = (byte)(((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) ? 255 : 0); + if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { + nextItem.m_methodDistance = 0f; + } else { + nextItem.m_methodDistance = methodDistance + transitionCost; + } + + if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) + nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; + continue; + } + + // NON-STOCK CODE START + if (applyTransportTransferPenalty) { + if ( + isMiddle && + (nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None && + (item.m_lanesUsed & NetInfo.LaneType.PublicTransport) != NetInfo.LaneType.None && + nextLaneInfo.m_laneType == NetInfo.LaneType.PublicTransport + ) { + // apply penalty when switching between public transport lines + float transportTransitionPenalty = (m_conf.PathFinding.PublicTransportTransitionMinPenalty + ((float)nextNode.m_maxWaitTime * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR) * (m_conf.PathFinding.PublicTransportTransitionMaxPenalty - m_conf.PathFinding.PublicTransportTransitionMinPenalty)) / (0.5f * this.m_maxLength); + transitionCostOverMeanMaxSpeed += transportTransitionPenalty; + +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied transport transfer penalty on PT change\n" + + "\t" + $"transportTransitionPenalty={transportTransitionPenalty}\n" + + "\t" + $"transitionCostOverMeanMaxSpeed={transitionCostOverMeanMaxSpeed}\n" + + "\t" + $"isMiddle={isMiddle}\n" + + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}\n" + + "\t" + $"prevLaneType={prevLaneType}\n" + + "\t" + $"item.m_lanesUsed={item.m_lanesUsed}\n" + + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}" + ); + } +#endif + } else if ( + (nextLaneId == m_startLaneA || nextLaneId == m_startLaneB) && + (item.m_lanesUsed & (NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport)) == NetInfo.LaneType.Pedestrian + ) { + // account for public tranport transition costs on non-PT paths + float transportTransitionPenalty = (2f * m_conf.PathFinding.PublicTransportTransitionMaxPenalty) / (0.5f * this.m_maxLength); + transitionCostOverMeanMaxSpeed += transportTransitionPenalty; +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied transport transfer penalty on non-PT path\n" + + "\t" + $"transportTransitionPenalty={transportTransitionPenalty}\n" + + "\t" + $"transitionCostOverMeanMaxSpeed={transitionCostOverMeanMaxSpeed}" + ); + } +#endif + } + } + // NON-STOCK CODE END + + nextItem.m_comparisonValue = comparisonValue + transitionCostOverMeanMaxSpeed; + nextItem.m_duration = duration + transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); + nextItem.m_direction = nextDir; + + if (nextLaneId == m_startLaneA) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Skipping: Invalid offset/direction on start lane A\n" + + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + + "\t" + $"m_startOffsetA={m_startOffsetA}" + ); + } +#endif + + nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; + continue; + } + + float nextLaneSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); + float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextLaneSpeed * m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextLaneSpeed; + } + + if (nextLaneId == m_startLaneB) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Skipping: Invalid offset/direction on start lane B\n" + + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + + "\t" + $"m_startOffsetB={m_startOffsetB}" + ); + } +#endif + + nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; + continue; + } + + float nextLaneSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); + float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextLaneSpeed * m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextLaneSpeed; + } + + if ( + !m_ignoreBlocked && (nextSegment.m_flags & NetSegment.Flags.Blocked) != NetSegment.Flags.None && + (nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None + ) { + nextItem.m_comparisonValue += 0.1f; + blocked = true; + } + + nextItem.m_laneID = nextLaneId; + nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); #if PARKINGAI - nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); + nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); #endif #if ADVANCEDAI && ROUTING - // NON-STOCK CODE START - nextItem.m_trafficRand = item.m_trafficRand; - // NON-STOCK CODE END + // NON-STOCK CODE START + nextItem.m_trafficRand = item.m_trafficRand; + // NON-STOCK CODE END #endif #if ROUTING #if ADVANCEDAI - if (enableAdvancedAI) { - float adjustedBaseLength = baseLength; - if (m_queueItem.spawned || (nextLaneId != m_startLaneA && nextLaneId != m_startLaneB)) { - if (laneDist != 0) { - // apply lane changing costs - adjustedBaseLength *= - 1f + - laneDist * - laneChangingCost * - (laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f); // additional costs for changing multiple lanes at once - } - } - - nextItem.m_comparisonValue += adjustedBaseLength; - -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied Advanced Vehicle AI\n" - + "\t" + $"baseLength={baseLength}\n" - + "\t" + $"adjustedBaseLength={adjustedBaseLength}\n" - + "\t" + $"laneDist={laneDist}\n" - + "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - } else -#endif - if (m_stablePath) { - // all non-road vehicles with stable paths (trains, trams, etc.): apply lane distance factor - float adjustedBaseLength = baseLength; - adjustedBaseLength *= 1 + laneDist; - nextItem.m_comparisonValue += adjustedBaseLength; - -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied stable path lane distance costs\n" - + "\t" + $"baseLength={baseLength}\n" - + "\t" + $"adjustedBaseLength={adjustedBaseLength}\n" - + "\t" + $"laneDist={laneDist}" - ); - } -#endif - } -#endif - - if ( - (nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && - (nextLaneInfo.m_vehicleType & m_vehicleTypes) != VehicleInfo.VehicleType.None - ) { + if (enableAdvancedAI) { + float adjustedBaseLength = baseLength; + if (m_queueItem.spawned || (nextLaneId != m_startLaneA && nextLaneId != m_startLaneB)) { + if (laneDist != 0) { + // apply lane changing costs + adjustedBaseLength *= + 1f + + laneDist * + laneChangingCost * + (laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f); // additional costs for changing multiple lanes at once + } + } + + nextItem.m_comparisonValue += adjustedBaseLength; + +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied Advanced Vehicle AI\n" + + "\t" + $"baseLength={baseLength}\n" + + "\t" + $"adjustedBaseLength={adjustedBaseLength}\n" + + "\t" + $"laneDist={laneDist}\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + } else +#endif + if (m_stablePath) { + // all non-road vehicles with stable paths (trains, trams, etc.): apply lane distance factor + float adjustedBaseLength = baseLength; + adjustedBaseLength *= 1 + laneDist; + nextItem.m_comparisonValue += adjustedBaseLength; + +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied stable path lane distance costs\n" + + "\t" + $"baseLength={baseLength}\n" + + "\t" + $"adjustedBaseLength={adjustedBaseLength}\n" + + "\t" + $"laneDist={laneDist}" + ); + } +#endif + } +#endif + + if ( + (nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && + (nextLaneInfo.m_vehicleType & m_vehicleTypes) != VehicleInfo.VehicleType.None + ) { #if ADVANCEDAI && ROUTING - if (! enableAdvancedAI) { + if (! enableAdvancedAI) { #endif - int firstTarget = netManager.m_lanes.m_buffer[nextLaneId].m_firstTarget; - int lastTarget = netManager.m_lanes.m_buffer[nextLaneId].m_lastTarget; - if (laneIndexFromInner < firstTarget || laneIndexFromInner >= lastTarget) { - nextItem.m_comparisonValue += Mathf.Max(1f, transitionCost * 3f - 3f) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); - } + int firstTarget = netManager.m_lanes.m_buffer[nextLaneId].m_firstTarget; + int lastTarget = netManager.m_lanes.m_buffer[nextLaneId].m_lastTarget; + if (laneIndexFromInner < firstTarget || laneIndexFromInner >= lastTarget) { + nextItem.m_comparisonValue += Mathf.Max(1f, transitionCost * 3f - 3f) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); + } #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: stock lane change costs\n" - + "\t" + $"firstTarget={firstTarget}\n" - + "\t" + $"lastTarget={lastTarget}\n" - + "\t" + $"laneIndexFromInner={laneIndexFromInner}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: stock lane change costs\n" + + "\t" + $"firstTarget={firstTarget}\n" + + "\t" + $"lastTarget={lastTarget}\n" + + "\t" + $"laneIndexFromInner={laneIndexFromInner}" + ); + } #endif #if ADVANCEDAI && ROUTING - } + } #endif - if ( - !m_transportVehicle && - nextLaneInfo.m_laneType == NetInfo.LaneType.TransportVehicle - ) { - nextItem.m_comparisonValue += 20f / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); - } - } + if ( + !m_transportVehicle && + nextLaneInfo.m_laneType == NetInfo.LaneType.TransportVehicle + ) { + nextItem.m_comparisonValue += 20f / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); + } + } #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Adding next item\n" - + "\t" + $"nextItem={nextItem}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Adding next item\n" + + "\t" + $"nextItem={nextItem}" + ); + } #endif - AddBufferItem( + AddBufferItem( #if DEBUG - debug, + debug, #endif - nextItem, item.m_position - ); - } else { + nextItem, item.m_position + ); + } else { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Lane type and/or vehicle type mismatch or same segment/lane. Skipping." - + "\t" + $"allowedLaneTypes={allowedLaneTypes}\n" - + "\t" + $"allowedVehicleTypes={allowedVehicleTypes}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Lane type and/or vehicle type mismatch or same segment/lane. Skipping." + + "\t" + $"allowedLaneTypes={allowedLaneTypes}\n" + + "\t" + $"allowedVehicleTypes={allowedVehicleTypes}" + ); + } #endif - } - } else { + } + } else { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Lane direction mismatch. Skipping." - + "\t" + $"nextLaneInfo.m_finalDirection={nextLaneInfo.m_finalDirection}\n" - + "\t" + $"nextFinalDir={nextFinalDir}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Lane direction mismatch. Skipping." + + "\t" + $"nextLaneInfo.m_finalDirection={nextLaneInfo.m_finalDirection}\n" + + "\t" + $"nextFinalDir={nextFinalDir}" + ); + } #endif - if ((nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && (nextLaneInfo.m_vehicleType & prevVehicleType) != VehicleInfo.VehicleType.None) { - newLaneIndexFromInner++; - } - } + if ((nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && (nextLaneInfo.m_vehicleType & prevVehicleType) != VehicleInfo.VehicleType.None) { + newLaneIndexFromInner++; + } + } - nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; - continue; - } - laneIndexFromInner = newLaneIndexFromInner; - return blocked; - } + nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; + continue; + } + laneIndexFromInner = newLaneIndexFromInner; + return blocked; + } - // 4 - private void ProcessItemPedBicycle( + // 4 + private void ProcessItemPedBicycle( #if DEBUG - bool debug, uint unitId, + bool debug, uint unitId, #endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextSegmentId, ref NetSegment nextSegment, ushort nextNodeId, ref NetNode nextNode, int nextLaneIndex, uint nextLaneId, ref NetLane nextLane, byte connectOffset, byte laneSwitchOffset) { + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextSegmentId, ref NetSegment nextSegment, ushort nextNodeId, ref NetNode nextNode, int nextLaneIndex, uint nextLaneId, ref NetLane nextLane, byte connectOffset, byte laneSwitchOffset) { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle called.\n" - + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" - + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" - + "\t" + $"nextSegmentId={nextSegmentId}\n" - + "\t" + $"nextNodeId={nextNodeId}\n" - + "\t" + $"nextLaneIndex={nextLaneIndex}\n" - + "\t" + $"nextLaneId={nextLaneId}\n" - + "\t" + $"connectOffset={connectOffset}\n" - + "\t" + $"laneSwitchOffset={laneSwitchOffset}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle called.\n" + + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" + + "\t" + $"nextSegmentId={nextSegmentId}\n" + + "\t" + $"nextNodeId={nextNodeId}\n" + + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + + "\t" + $"nextLaneId={nextLaneId}\n" + + "\t" + $"connectOffset={connectOffset}\n" + + "\t" + $"laneSwitchOffset={laneSwitchOffset}" + ); + } #endif - if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { + if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Disable mask\n" - + "\t" + $"m_disableMask={m_disableMask}\n" - + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Disable mask\n" + + "\t" + $"m_disableMask={m_disableMask}\n" + + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); + } #endif - return; - } + return; + } - // NON-STOCK CODE START - bool mayCross = true; + // NON-STOCK CODE START + bool mayCross = true; #if JUNCTIONRESTRICTIONS || CUSTOMTRAFFICLIGHTS - if (Options.junctionRestrictionsEnabled || Options.timedLightsEnabled) { - bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; - if (nextIsStartNode || nextNodeId == nextSegment.m_endNode) { + if (Options.junctionRestrictionsEnabled || Options.timedLightsEnabled) { + bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; + if (nextIsStartNode || nextNodeId == nextSegment.m_endNode) { #if JUNCTIONRESTRICTIONS - if (Options.junctionRestrictionsEnabled && item.m_position.m_segment == nextSegmentId) { - // check if pedestrians are not allowed to cross here - if (!m_junctionManager.IsPedestrianCrossingAllowed(nextSegmentId, nextIsStartNode)) { + if (Options.junctionRestrictionsEnabled && item.m_position.m_segment == nextSegmentId) { + // check if pedestrians are not allowed to cross here + if (!m_junctionManager.IsPedestrianCrossingAllowed(nextSegmentId, nextIsStartNode)) { #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Pedestrian crossing prohibited"); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Pedestrian crossing prohibited"); + } #endif - mayCross = false; - } - } + mayCross = false; + } + } #endif #if CUSTOMTRAFFICLIGHTS - if (Options.timedLightsEnabled) { - // check if pedestrian light won't change to green - ICustomSegmentLights lights = m_customTrafficLightsManager.GetSegmentLights(nextSegmentId, nextIsStartNode, false); - if (lights != null && lights.InvalidPedestrianLight) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid pedestrian light"); - } -#endif - return; - } - } -#endif - } - } -#endif - // NON-STOCK CODE END - - NetInfo nextSegmentInfo = nextSegment.Info; - NetInfo prevSegmentInfo = prevSegment.Info; - int nextNumLanes = nextSegmentInfo.m_lanes.Length; - float distance; - byte offset; - if (nextSegmentId == item.m_position.m_segment) { - Vector3 b = prevLane.CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - Vector3 a = nextLane.CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - distance = Vector3.Distance(a, b); - offset = connectOffset; - } else { - NetInfo.Direction direction = (NetInfo.Direction)((nextNodeId != nextSegment.m_startNode) ? 1 : 2); - Vector3 b = prevLane.CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - Vector3 a = ((direction & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? nextLane.m_bezier.a : nextLane.m_bezier.d; - distance = Vector3.Distance(a, b); - offset = (byte)(((direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) ? 255 : 0); - } - - // float prevMaxSpeed = 1f; // stock code commented - // float prevLaneSpeed = 1f; // stock code commented - NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; - if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { - NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; - // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented - // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, laneSwitchOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented - prevLaneType = prevLaneInfo.m_laneType; - if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { - prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } - } - - float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; - float offsetLength = (float)Mathf.Abs(laneSwitchOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; - float methodDistance = item.m_methodDistance + offsetLength; - float comparisonValue = item.m_comparisonValue + offsetLength / (prevLaneSpeed * m_maxLength); - float duration = item.m_duration + offsetLength / prevMaxSpeed; - - if (!m_ignoreCost) { - int ticketCost = prevLane.m_ticketCost; - if (ticketCost != 0) { - comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; - } - } - - if (nextLaneIndex < nextNumLanes) { - NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; - BufferItem nextItem = default(BufferItem); - - nextItem.m_position.m_segment = nextSegmentId; - nextItem.m_position.m_lane = (byte)nextLaneIndex; - nextItem.m_position.m_offset = offset; - - // NON-STOCK CODE START - if (!mayCross && nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Crossing prohibited"); - } -#endif - return; - } - // NON-STOCK CODE END - - if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { - nextItem.m_methodDistance = 0f; - } else { - if (item.m_methodDistance == 0f) { - comparisonValue += 100f / (0.25f * m_maxLength); - } - nextItem.m_methodDistance = methodDistance + distance; - } - - if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Max. walking distance exceeded\n" - + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}" - ); - } -#endif - return; - } - - float nextMaxSpeed; + if (Options.timedLightsEnabled) { + // check if pedestrian light won't change to green + ICustomSegmentLights lights = m_customTrafficLightsManager.GetSegmentLights(nextSegmentId, nextIsStartNode, false); + if (lights != null && lights.InvalidPedestrianLight) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid pedestrian light"); + } +#endif + return; + } + } +#endif + } + } +#endif + // NON-STOCK CODE END + + NetInfo nextSegmentInfo = nextSegment.Info; + NetInfo prevSegmentInfo = prevSegment.Info; + int nextNumLanes = nextSegmentInfo.m_lanes.Length; + float distance; + byte offset; + if (nextSegmentId == item.m_position.m_segment) { + Vector3 b = prevLane.CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + Vector3 a = nextLane.CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + distance = Vector3.Distance(a, b); + offset = connectOffset; + } else { + NetInfo.Direction direction = (NetInfo.Direction)((nextNodeId != nextSegment.m_startNode) ? 1 : 2); + Vector3 b = prevLane.CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + Vector3 a = ((direction & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? nextLane.m_bezier.a : nextLane.m_bezier.d; + distance = Vector3.Distance(a, b); + offset = (byte)(((direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) ? 255 : 0); + } + + // float prevMaxSpeed = 1f; // stock code commented + // float prevLaneSpeed = 1f; // stock code commented + NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; + if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { + NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; + // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented + // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, laneSwitchOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented + prevLaneType = prevLaneInfo.m_laneType; + if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { + prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } + } + + float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; + float offsetLength = (float)Mathf.Abs(laneSwitchOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; + float methodDistance = item.m_methodDistance + offsetLength; + float comparisonValue = item.m_comparisonValue + offsetLength / (prevLaneSpeed * m_maxLength); + float duration = item.m_duration + offsetLength / prevMaxSpeed; + + if (!m_ignoreCost) { + int ticketCost = prevLane.m_ticketCost; + if (ticketCost != 0) { + comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; + } + } + + if (nextLaneIndex < nextNumLanes) { + NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; + BufferItem nextItem = default(BufferItem); + + nextItem.m_position.m_segment = nextSegmentId; + nextItem.m_position.m_lane = (byte)nextLaneIndex; + nextItem.m_position.m_offset = offset; + + // NON-STOCK CODE START + if (!mayCross && nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Crossing prohibited"); + } +#endif + return; + } + // NON-STOCK CODE END + + if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { + nextItem.m_methodDistance = 0f; + } else { + if (item.m_methodDistance == 0f) { + comparisonValue += 100f / (0.25f * m_maxLength); + } + nextItem.m_methodDistance = methodDistance + distance; + } + + if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Max. walking distance exceeded\n" + + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}" + ); + } +#endif + return; + } + + float nextMaxSpeed; #if SPEEDLIMITS - // NON-STOCK CODE START - nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); - // NON-STOCK CODE END + // NON-STOCK CODE START + nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); + // NON-STOCK CODE END #else nextMaxSpeed = nextLaneInfo.m_speedLimit; #endif - nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.25f * m_maxLength); - nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); - - if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { - nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); - } else { - nextItem.m_direction = nextLaneInfo.m_finalDirection; - } - - if (nextLaneId == m_startLaneA) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid offset/direction on start lane A\n" - + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" - + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" - + "\t" + $"m_startOffsetA={m_startOffsetA}" - ); - } -#endif - return; - } - - float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); - float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; - } - - if (nextLaneId == m_startLaneB) { - if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && - ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { -#if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid offset/direction on start lane B\n" - + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" - + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" - + "\t" + $"m_startOffsetB={m_startOffsetB}" - ); - } -#endif - return; - } - - float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); - float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - - nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); - nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; - } - - nextItem.m_laneID = nextLaneId; - nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); + nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.25f * m_maxLength); + nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); + + if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { + nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); + } else { + nextItem.m_direction = nextLaneInfo.m_finalDirection; + } + + if (nextLaneId == m_startLaneA) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid offset/direction on start lane A\n" + + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + + "\t" + $"m_startOffsetA={m_startOffsetA}" + ); + } +#endif + return; + } + + float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); + float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; + } + + if (nextLaneId == m_startLaneB) { + if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && + ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid offset/direction on start lane B\n" + + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + + "\t" + $"m_startOffsetB={m_startOffsetB}" + ); + } +#endif + return; + } + + float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); + float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + + nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); + nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; + } + + nextItem.m_laneID = nextLaneId; + nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); #if PARKINGAI - nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); + nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); #endif #if ADVANCEDAI && ROUTING - // NON-STOCK CODE START - nextItem.m_trafficRand = item.m_trafficRand; - // NON-STOCK CODE END + // NON-STOCK CODE START + nextItem.m_trafficRand = item.m_trafficRand; + // NON-STOCK CODE END #endif #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Adding next item\n" - + "\t" + $"nextItem={nextItem}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Adding next item\n" + + "\t" + $"nextItem={nextItem}" + ); + } #endif - AddBufferItem( + AddBufferItem( #if DEBUG - debug, + debug, #endif - nextItem, item.m_position - ); - } - } + nextItem, item.m_position + ); + } + } #if ROUTING - // 5 (custom: process routed vehicle paths) - private bool ProcessItemRouted( + // 5 (custom: process routed vehicle paths) + private bool ProcessItemRouted( #if DEBUG - bool debug, uint unitId, + bool debug, uint unitId, #endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, #if ADVANCEDAI - bool enableAdvancedAI, float laneChangingCost, + bool enableAdvancedAI, float laneChangingCost, #endif - float segmentSelectionCost, float laneSelectionCost, ushort nextNodeId, ref NetNode nextNode, bool isMiddle, SegmentRoutingData prevSegmentRouting, LaneEndRoutingData prevLaneEndRouting, byte connectOffset, int prevInnerSimilarLaneIndex - ) { + float segmentSelectionCost, float laneSelectionCost, ushort nextNodeId, ref NetNode nextNode, bool isMiddle, SegmentRoutingData prevSegmentRouting, LaneEndRoutingData prevLaneEndRouting, byte connectOffset, int prevInnerSimilarLaneIndex + ) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted called.\n" - + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" - + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" + if (debug) { + Debug(unitId, item, $"ProcessItemRouted called.\n" + + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" #if ADVANCEDAI - + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" - + "\t" + $"laneChangingCost={laneChangingCost}\n" -#endif - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}\n" - + "\t" + $"nextNodeId={nextNodeId}\n" - + "\t" + $"isMiddle={isMiddle}\n" - + "\t" + $"prevSegmentRouting={prevSegmentRouting}\n" - + "\t" + $"prevLaneEndRouting={prevLaneEndRouting}\n" - + "\t" + $"connectOffset={connectOffset}\n" - + "\t" + $"prevInnerSimilarLaneIndex={prevInnerSimilarLaneIndex}\n" - ); - } -#endif - - /* - * ======================================================================================================= - * Fetch lane end transitions, check if there are any present - * ======================================================================================================= - */ - LaneTransitionData[] laneTransitions = prevLaneEndRouting.transitions; - if (laneTransitions == null) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Aborting: No lane transitions"); - } -#endif - return false; - } - - ushort prevSegmentId = item.m_position.m_segment; - int prevLaneIndex = item.m_position.m_lane; - NetInfo prevSegmentInfo = prevSegment.Info; - if (prevLaneIndex >= prevSegmentInfo.m_lanes.Length) { -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Aborting: Invalid lane index"); - } -#endif - return false; - } - NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; + + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + + "\t" + $"laneChangingCost={laneChangingCost}\n" +#endif + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"nextNodeId={nextNodeId}\n" + + "\t" + $"isMiddle={isMiddle}\n" + + "\t" + $"prevSegmentRouting={prevSegmentRouting}\n" + + "\t" + $"prevLaneEndRouting={prevLaneEndRouting}\n" + + "\t" + $"connectOffset={connectOffset}\n" + + "\t" + $"prevInnerSimilarLaneIndex={prevInnerSimilarLaneIndex}\n" + ); + } +#endif + + /* + * ======================================================================================================= + * Fetch lane end transitions, check if there are any present + * ======================================================================================================= + */ + LaneTransitionData[] laneTransitions = prevLaneEndRouting.transitions; + if (laneTransitions == null) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Aborting: No lane transitions"); + } +#endif + return false; + } + + ushort prevSegmentId = item.m_position.m_segment; + int prevLaneIndex = item.m_position.m_lane; + NetInfo prevSegmentInfo = prevSegment.Info; + if (prevLaneIndex >= prevSegmentInfo.m_lanes.Length) { +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Aborting: Invalid lane index"); + } +#endif + return false; + } + NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; #if VEHICLERESTRICTIONS - /* - * ======================================================================================================= - * Check vehicle restrictions, especially bans - * ======================================================================================================= - */ - bool canUseLane = CanUseLane(prevSegmentId, prevSegmentInfo, prevLaneIndex, prevLaneInfo); - if (! canUseLane && Options.vehicleRestrictionsAggression == VehicleRestrictionsAggression.Strict) { - // vehicle is strictly prohibited to use this lane -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Vehicle restrictions: Aborting: Strict vehicle restrictions active"); - } -#endif - return false; - } -#endif - - bool strictLaneRouting = - m_isLaneArrowObeyingEntity && - nextNode.Info.m_class.m_service != ItemClass.Service.Beautification && - (nextNode.m_flags & NetNode.Flags.Untouchable) == NetNode.Flags.None - ; - bool prevIsCarLane = - (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && - (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None - ; - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Strict lane routing? {strictLaneRouting}\n" - + "\t" + $"m_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n" - + "\t" + $"nextNode.Info.m_class.m_service={nextNode.Info.m_class.m_service}\n" - + "\t" + $"nextNode.m_flags={nextNode.m_flags}\n" - + "\t" + $"prevIsCarLane={prevIsCarLane}" - ); - } -#endif - - /* - * ======================================================================================================= - * Check if u-turns may be performed - * ======================================================================================================= - */ - bool isUturnAllowedHere = false; // is u-turn allowed at this place? - if ((this.m_vehicleTypes & (VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None) { // is vehicle able to perform a u-turn? - bool isStockUturnPoint = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; + /* + * ======================================================================================================= + * Check vehicle restrictions, especially bans + * ======================================================================================================= + */ + bool canUseLane = CanUseLane(prevSegmentId, prevSegmentInfo, prevLaneIndex, prevLaneInfo); + if (! canUseLane && Options.vehicleRestrictionsAggression == VehicleRestrictionsAggression.Strict) { + // vehicle is strictly prohibited to use this lane +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Vehicle restrictions: Aborting: Strict vehicle restrictions active"); + } +#endif + return false; + } +#endif + + bool strictLaneRouting = + m_isLaneArrowObeyingEntity && + nextNode.Info.m_class.m_service != ItemClass.Service.Beautification && + (nextNode.m_flags & NetNode.Flags.Untouchable) == NetNode.Flags.None + ; + bool prevIsCarLane = + (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && + (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None + ; + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Strict lane routing? {strictLaneRouting}\n" + + "\t" + $"m_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n" + + "\t" + $"nextNode.Info.m_class.m_service={nextNode.Info.m_class.m_service}\n" + + "\t" + $"nextNode.m_flags={nextNode.m_flags}\n" + + "\t" + $"prevIsCarLane={prevIsCarLane}" + ); + } +#endif + + /* + * ======================================================================================================= + * Check if u-turns may be performed + * ======================================================================================================= + */ + bool isUturnAllowedHere = false; // is u-turn allowed at this place? + if ((this.m_vehicleTypes & (VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None) { // is vehicle able to perform a u-turn? + bool isStockUturnPoint = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; #if JUNCTIONRESTRICTIONS - if (Options.junctionRestrictionsEnabled) { - bool nextIsStartNode = nextNodeId == prevSegment.m_startNode; - bool prevIsOutgoingOneWay = nextIsStartNode ? prevSegmentRouting.startNodeOutgoingOneWay : prevSegmentRouting.endNodeOutgoingOneWay; - - // determine if the vehicle may u-turn at the target node, according to customization - isUturnAllowedHere = - m_isRoadVehicle && // only road vehicles may perform u-turns - prevIsCarLane && // u-turns for road vehicles only - (!m_isHeavyVehicle || isStockUturnPoint) && // only small vehicles may perform u-turns OR everyone at stock u-turn points - !prevIsOutgoingOneWay && // do not u-turn on one-ways - ( - m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode) - /*|| // only do u-turns if allowed - (!m_queueItem.spawned && // or a yet unspawned vehicle ... - (prevSegmentId == m_startSegmentA || prevSegmentId == m_startSegmentB)) // ... starts at the current segment*/ - ) - ; - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Junction restrictions: Is u-turn allowed here? {isUturnAllowedHere}\n" - + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" - + "\t" + $"prevIsCarLane={prevIsCarLane}\n" - + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" - + "\t" + $"isStockUturnPoint={isStockUturnPoint}\n" - + "\t" + $"prevIsOutgoingOneWay={prevIsOutgoingOneWay}\n" - + "\t" + $"m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)={m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)}\n" - + "\t" + $"m_queueItem.vehicleId={m_queueItem.vehicleId}\n" - + "\t" + $"m_queueItem.spawned={m_queueItem.spawned}\n" - + "\t" + $"prevSegmentId={prevSegmentId}\n" - + "\t" + $"m_startSegmentA={m_startSegmentA}\n" - + "\t" + $"m_startSegmentB={m_startSegmentB}" - ); - } -#endif - } else { -#endif - isUturnAllowedHere = isStockUturnPoint; - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Junction restrictions disabled: Is u-turn allowed here? {isUturnAllowedHere}"); - } + if (Options.junctionRestrictionsEnabled) { + bool nextIsStartNode = nextNodeId == prevSegment.m_startNode; + bool prevIsOutgoingOneWay = nextIsStartNode ? prevSegmentRouting.startNodeOutgoingOneWay : prevSegmentRouting.endNodeOutgoingOneWay; + + // determine if the vehicle may u-turn at the target node, according to customization + isUturnAllowedHere = + m_isRoadVehicle && // only road vehicles may perform u-turns + prevIsCarLane && // u-turns for road vehicles only + (!m_isHeavyVehicle || isStockUturnPoint) && // only small vehicles may perform u-turns OR everyone at stock u-turn points + !prevIsOutgoingOneWay && // do not u-turn on one-ways + ( + m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode) + /*|| // only do u-turns if allowed + (!m_queueItem.spawned && // or a yet unspawned vehicle ... + (prevSegmentId == m_startSegmentA || prevSegmentId == m_startSegmentB)) // ... starts at the current segment*/ + ) + ; + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Junction restrictions: Is u-turn allowed here? {isUturnAllowedHere}\n" + + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"isStockUturnPoint={isStockUturnPoint}\n" + + "\t" + $"prevIsOutgoingOneWay={prevIsOutgoingOneWay}\n" + + "\t" + $"m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)={m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)}\n" + + "\t" + $"m_queueItem.vehicleId={m_queueItem.vehicleId}\n" + + "\t" + $"m_queueItem.spawned={m_queueItem.spawned}\n" + + "\t" + $"prevSegmentId={prevSegmentId}\n" + + "\t" + $"m_startSegmentA={m_startSegmentA}\n" + + "\t" + $"m_startSegmentB={m_startSegmentB}" + ); + } +#endif + } else { +#endif + isUturnAllowedHere = isStockUturnPoint; + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Junction restrictions disabled: Is u-turn allowed here? {isUturnAllowedHere}"); + } #endif #if JUNCTIONRESTRICTIONS - } + } #endif - } + } #if VEHICLERESTRICTIONS - /* - * ======================================================================================================= - * Apply vehicle restriction costs - * ======================================================================================================= - */ - if (!canUseLane) { - laneSelectionCost *= VehicleRestrictionsManager.PATHFIND_PENALTIES[(int)Options.vehicleRestrictionsAggression]; -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Vehicle restrictions: Applied lane costs\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}" - ); - } -#endif - } -#endif - - /* - * ======================================================================================================= - * Apply costs for large vehicles using inner lanes on highways - * ======================================================================================================= - */ - if (Options.preferOuterLane && - m_isHeavyVehicle && - m_isRoadVehicle && - prevIsCarLane && - prevSegmentRouting.highway && - prevLaneInfo.m_similarLaneCount > 1 && - m_pathRandomizer.Int32(m_conf.PathFinding.HeavyVehicleInnerLanePenaltySegmentSel) == 0) { - - int prevOuterSimilarLaneIndex = m_routingManager.CalcOuterSimilarLaneIndex(prevLaneInfo); - float prevRelOuterLane = ((float)prevOuterSimilarLaneIndex / (float)(prevLaneInfo.m_similarLaneCount - 1)); - laneSelectionCost *= 1f + m_conf.PathFinding.HeavyVehicleMaxInnerLanePenalty * prevRelOuterLane; - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Heavy trucks prefer outer lanes on highways: Applied lane costs\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}\n" - + "\t" + $"Options.preferOuterLane={Options.preferOuterLane}\n" - + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" - + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" - + "\t" + $"prevIsCarLane={prevIsCarLane}\n" - + "\t" + $"prevSegmentRouting.highway={prevSegmentRouting.highway}\n" - + "\t" + $"prevLaneInfo.m_similarLaneCount={prevLaneInfo.m_similarLaneCount}\n" - + "\t" + $"prevOuterSimilarLaneIndex={prevOuterSimilarLaneIndex}\n" - + "\t" + $"prevRelOuterLane={prevRelOuterLane}" - ); - } -#endif - } - -#if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemRouted: Final cost factors:\n" - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}\n" - + "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - - /* - * ======================================================================================================= - * Explore available lane end routings - * ======================================================================================================= - */ - NetManager netManager = Singleton.instance; - bool blocked = false; - bool uturnExplored = false; - for (int k = 0; k < laneTransitions.Length; ++k) { -#if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Exploring lane transition #{k}: {laneTransitions[k]}"); - } -#endif - - ushort nextSegmentId = laneTransitions[k].segmentId; - - if (nextSegmentId == 0) { - continue; - } - - if (laneTransitions[k].type == LaneEndTransitionType.Invalid) { -#if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Skipping transition: Transition is invalid"); - } -#endif - continue; - } - - if (nextSegmentId == prevSegmentId) { - if (!isUturnAllowedHere) { -#if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Skipping transition: U-turn is not allowed here"); - } -#endif - - // prevent double/forbidden exploration of previous segment by vanilla code during this method execution - continue; - } - -#if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Processing transition: Exploring u-turn"); - } -#endif - // we are going to explore a regular u-turn - uturnExplored = true; - } - - // allow vehicles to ignore strict lane routing when moving off - bool relaxedLaneRouting = - m_isRoadVehicle && - ((!m_queueItem.spawned || (m_queueItem.vehicleType & (ExtVehicleType.PublicTransport | ExtVehicleType.Emergency)) != ExtVehicleType.None) && - (laneTransitions[k].laneId == m_startLaneA || laneTransitions[k].laneId == m_startLaneB)); - -#if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Relaxed lane routing? {relaxedLaneRouting}\n" - + "\t" + $"relaxedLaneRouting={relaxedLaneRouting}\n" - + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" - + "\t" + $"m_queueItem.spawned={m_queueItem.spawned}\n" - + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" - + "\t" + $"m_queueItem.vehicleId={m_queueItem.vehicleId}\n" - + "\t" + $"m_startLaneA={m_startLaneA}\n" - + "\t" + $"m_startLaneB={m_startLaneB}" - ); - } + /* + * ======================================================================================================= + * Apply vehicle restriction costs + * ======================================================================================================= + */ + if (!canUseLane) { + laneSelectionCost *= VehicleRestrictionsManager.PATHFIND_PENALTIES[(int)Options.vehicleRestrictionsAggression]; +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Vehicle restrictions: Applied lane costs\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}" + ); + } +#endif + } +#endif + + /* + * ======================================================================================================= + * Apply costs for large vehicles using inner lanes on highways + * ======================================================================================================= + */ + if (Options.preferOuterLane && + m_isHeavyVehicle && + m_isRoadVehicle && + prevIsCarLane && + prevSegmentRouting.highway && + prevLaneInfo.m_similarLaneCount > 1 && + m_pathRandomizer.Int32(m_conf.PathFinding.HeavyVehicleInnerLanePenaltySegmentSel) == 0) { + + int prevOuterSimilarLaneIndex = m_routingManager.CalcOuterSimilarLaneIndex(prevLaneInfo); + float prevRelOuterLane = ((float)prevOuterSimilarLaneIndex / (float)(prevLaneInfo.m_similarLaneCount - 1)); + laneSelectionCost *= 1f + m_conf.PathFinding.HeavyVehicleMaxInnerLanePenalty * prevRelOuterLane; + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Heavy trucks prefer outer lanes on highways: Applied lane costs\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"Options.preferOuterLane={Options.preferOuterLane}\n" + + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" + + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + + "\t" + $"prevSegmentRouting.highway={prevSegmentRouting.highway}\n" + + "\t" + $"prevLaneInfo.m_similarLaneCount={prevLaneInfo.m_similarLaneCount}\n" + + "\t" + $"prevOuterSimilarLaneIndex={prevOuterSimilarLaneIndex}\n" + + "\t" + $"prevRelOuterLane={prevRelOuterLane}" + ); + } +#endif + } + +#if DEBUG + if (debug) { + Debug(unitId, item, $"ProcessItemRouted: Final cost factors:\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + + /* + * ======================================================================================================= + * Explore available lane end routings + * ======================================================================================================= + */ + NetManager netManager = Singleton.instance; + bool blocked = false; + bool uturnExplored = false; + for (int k = 0; k < laneTransitions.Length; ++k) { +#if DEBUG + if (debug) { + Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Exploring lane transition #{k}: {laneTransitions[k]}"); + } +#endif + + ushort nextSegmentId = laneTransitions[k].segmentId; + + if (nextSegmentId == 0) { + continue; + } + + if (laneTransitions[k].type == LaneEndTransitionType.Invalid) { +#if DEBUG + if (debug) { + Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Skipping transition: Transition is invalid"); + } +#endif + continue; + } + + if (nextSegmentId == prevSegmentId) { + if (!isUturnAllowedHere) { +#if DEBUG + if (debug) { + Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Skipping transition: U-turn is not allowed here"); + } +#endif + + // prevent double/forbidden exploration of previous segment by vanilla code during this method execution + continue; + } + +#if DEBUG + if (debug) { + Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Processing transition: Exploring u-turn"); + } +#endif + // we are going to explore a regular u-turn + uturnExplored = true; + } + + // allow vehicles to ignore strict lane routing when moving off + bool relaxedLaneRouting = + m_isRoadVehicle && + ((!m_queueItem.spawned || (m_queueItem.vehicleType & (ExtVehicleType.PublicTransport | ExtVehicleType.Emergency)) != ExtVehicleType.None) && + (laneTransitions[k].laneId == m_startLaneA || laneTransitions[k].laneId == m_startLaneB)); + +#if DEBUG + if (debug) { + Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Relaxed lane routing? {relaxedLaneRouting}\n" + + "\t" + $"relaxedLaneRouting={relaxedLaneRouting}\n" + + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + + "\t" + $"m_queueItem.spawned={m_queueItem.spawned}\n" + + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + + "\t" + $"m_queueItem.vehicleId={m_queueItem.vehicleId}\n" + + "\t" + $"m_startLaneA={m_startLaneA}\n" + + "\t" + $"m_startLaneB={m_startLaneB}" + ); + } #endif - if ( - !relaxedLaneRouting && - (strictLaneRouting && laneTransitions[k].type == LaneEndTransitionType.Relaxed) - ) { + if ( + !relaxedLaneRouting && + (strictLaneRouting && laneTransitions[k].type == LaneEndTransitionType.Relaxed) + ) { #if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Aborting: Cannot explore relaxed lane\n" - + "\t" + $"relaxedLaneRouting={relaxedLaneRouting}\n" - + "\t" + $"strictLaneRouting={strictLaneRouting}\n" - + "\t" + $"laneTransitions[k].type={laneTransitions[k].type}" - ); - } + if (debug) { + Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Aborting: Cannot explore relaxed lane\n" + + "\t" + $"relaxedLaneRouting={relaxedLaneRouting}\n" + + "\t" + $"strictLaneRouting={strictLaneRouting}\n" + + "\t" + $"laneTransitions[k].type={laneTransitions[k].type}" + ); + } #endif - continue; - } + continue; + } #if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Exploring lane transition now\n" + if (debug) { + Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Exploring lane transition now\n" #if ADVANCEDAI - + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" - + "\t" + $"laneChangingCost={laneChangingCost}\n" + + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + + "\t" + $"laneChangingCost={laneChangingCost}\n" #endif - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}" - ); - } + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}" + ); + } #endif - if ( - ProcessItemCosts( + if ( + ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI - enableAdvancedAI, laneChangingCost, -#endif - nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], segmentSelectionCost, laneSelectionCost, laneTransitions[k], ref prevInnerSimilarLaneIndex, connectOffset, true, false - ) - ) { - blocked = true; - } - } - - return blocked && !uturnExplored; - } -#endif - - private void AddBufferItem( -#if DEBUG - bool debug, -#endif - BufferItem item, PathUnit.Position target - ) { -#if DEBUG - if (debug) { - m_debugPositions[target.m_segment].Add(item.m_position.m_segment); - } -#endif - - uint laneLocation = m_laneLocation[item.m_laneID]; - uint locPathFindIndex = laneLocation >> 16; // upper 16 bit, expected (?) path find index - int bufferIndex = (int)(laneLocation & 65535u); // lower 16 bit - int comparisonBufferPos; - - if (locPathFindIndex == m_pathFindIndex) { - if (item.m_comparisonValue >= m_buffer[bufferIndex].m_comparisonValue) { - return; - } - - int bufferPosIndex = bufferIndex >> 6; // arithmetic shift (sign stays), upper 10 bit - int bufferPos = bufferIndex & -64; // upper 10 bit (no shift) - if (bufferPosIndex < m_bufferMinPos || (bufferPosIndex == m_bufferMinPos && bufferPos < m_bufferMin[bufferPosIndex])) { - return; - } - - comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); - if (comparisonBufferPos == bufferPosIndex) { - m_buffer[bufferIndex] = item; - m_laneTarget[item.m_laneID] = target; - return; - } - - int newBufferIndex = bufferPosIndex << 6 | m_bufferMax[bufferPosIndex]--; - BufferItem bufferItem = m_buffer[newBufferIndex]; - m_laneLocation[bufferItem.m_laneID] = laneLocation; - m_buffer[bufferIndex] = bufferItem; - } else { - comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); - } - - if (comparisonBufferPos >= 1024 || comparisonBufferPos < 0) { - return; - } - - while (m_bufferMax[comparisonBufferPos] == 63) { - ++comparisonBufferPos; - if (comparisonBufferPos == 1024) { - return; - } - } - - if (comparisonBufferPos > m_bufferMaxPos) { - m_bufferMaxPos = comparisonBufferPos; - } - - bufferIndex = (comparisonBufferPos << 6 | ++m_bufferMax[comparisonBufferPos]); - m_buffer[bufferIndex] = item; - m_laneLocation[item.m_laneID] = (m_pathFindIndex << 16 | (uint)bufferIndex); - m_laneTarget[item.m_laneID] = target; - } - - private float CalculateLaneSpeed(float maxSpeed, byte startOffset, byte endOffset, ref NetSegment segment, NetInfo.Lane laneInfo) { - NetInfo.Direction direction = ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); - if ((direction & NetInfo.Direction.Avoid) != NetInfo.Direction.None) { - if (endOffset > startOffset && direction == NetInfo.Direction.AvoidForward) { - return maxSpeed * 0.1f; - } - if (endOffset < startOffset && direction == NetInfo.Direction.AvoidBackward) { - return maxSpeed * 0.1f; - } - return maxSpeed * 0.2f; - } - return maxSpeed; - } - - private void GetLaneDirection(PathUnit.Position pathPos, out NetInfo.Direction direction, out NetInfo.LaneType laneType + enableAdvancedAI, laneChangingCost, +#endif + nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], segmentSelectionCost, laneSelectionCost, laneTransitions[k], ref prevInnerSimilarLaneIndex, connectOffset, true, false + ) + ) { + blocked = true; + } + } + + return blocked && !uturnExplored; + } +#endif + + private void AddBufferItem( +#if DEBUG + bool debug, +#endif + BufferItem item, PathUnit.Position target + ) { +#if DEBUG + if (debug) { + m_debugPositions[target.m_segment].Add(item.m_position.m_segment); + } +#endif + + uint laneLocation = m_laneLocation[item.m_laneID]; + uint locPathFindIndex = laneLocation >> 16; // upper 16 bit, expected (?) path find index + int bufferIndex = (int)(laneLocation & 65535u); // lower 16 bit + int comparisonBufferPos; + + if (locPathFindIndex == m_pathFindIndex) { + if (item.m_comparisonValue >= m_buffer[bufferIndex].m_comparisonValue) { + return; + } + + int bufferPosIndex = bufferIndex >> 6; // arithmetic shift (sign stays), upper 10 bit + int bufferPos = bufferIndex & -64; // upper 10 bit (no shift) + if (bufferPosIndex < m_bufferMinPos || (bufferPosIndex == m_bufferMinPos && bufferPos < m_bufferMin[bufferPosIndex])) { + return; + } + + comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); + if (comparisonBufferPos == bufferPosIndex) { + m_buffer[bufferIndex] = item; + m_laneTarget[item.m_laneID] = target; + return; + } + + int newBufferIndex = bufferPosIndex << 6 | m_bufferMax[bufferPosIndex]--; + BufferItem bufferItem = m_buffer[newBufferIndex]; + m_laneLocation[bufferItem.m_laneID] = laneLocation; + m_buffer[bufferIndex] = bufferItem; + } else { + comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); + } + + if (comparisonBufferPos >= 1024 || comparisonBufferPos < 0) { + return; + } + + while (m_bufferMax[comparisonBufferPos] == 63) { + ++comparisonBufferPos; + if (comparisonBufferPos == 1024) { + return; + } + } + + if (comparisonBufferPos > m_bufferMaxPos) { + m_bufferMaxPos = comparisonBufferPos; + } + + bufferIndex = (comparisonBufferPos << 6 | ++m_bufferMax[comparisonBufferPos]); + m_buffer[bufferIndex] = item; + m_laneLocation[item.m_laneID] = (m_pathFindIndex << 16 | (uint)bufferIndex); + m_laneTarget[item.m_laneID] = target; + } + + private float CalculateLaneSpeed(float maxSpeed, byte startOffset, byte endOffset, ref NetSegment segment, NetInfo.Lane laneInfo) { + NetInfo.Direction direction = ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); + if ((direction & NetInfo.Direction.Avoid) != NetInfo.Direction.None) { + if (endOffset > startOffset && direction == NetInfo.Direction.AvoidForward) { + return maxSpeed * 0.1f; + } + if (endOffset < startOffset && direction == NetInfo.Direction.AvoidBackward) { + return maxSpeed * 0.1f; + } + return maxSpeed * 0.2f; + } + return maxSpeed; + } + + private void GetLaneDirection(PathUnit.Position pathPos, out NetInfo.Direction direction, out NetInfo.LaneType laneType #if PARKINGAI - , out VehicleInfo.VehicleType vehicleType -#endif - ) { - NetManager netManager = Singleton.instance; - NetInfo info = netManager.m_segments.m_buffer[pathPos.m_segment].Info; - if (info.m_lanes.Length > pathPos.m_lane) { - direction = info.m_lanes[pathPos.m_lane].m_finalDirection; - laneType = info.m_lanes[pathPos.m_lane].m_laneType; + , out VehicleInfo.VehicleType vehicleType +#endif + ) { + NetManager netManager = Singleton.instance; + NetInfo info = netManager.m_segments.m_buffer[pathPos.m_segment].Info; + if (info.m_lanes.Length > pathPos.m_lane) { + direction = info.m_lanes[pathPos.m_lane].m_finalDirection; + laneType = info.m_lanes[pathPos.m_lane].m_laneType; #if PARKINGAI - vehicleType = info.m_lanes[pathPos.m_lane].m_vehicleType; -#endif - if ((netManager.m_segments.m_buffer[pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { - direction = NetInfo.InvertDirection(direction); - } - } else { - direction = NetInfo.Direction.None; - laneType = NetInfo.LaneType.None; + vehicleType = info.m_lanes[pathPos.m_lane].m_vehicleType; +#endif + if ((netManager.m_segments.m_buffer[pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { + direction = NetInfo.InvertDirection(direction); + } + } else { + direction = NetInfo.Direction.None; + laneType = NetInfo.LaneType.None; #if PARKINGAI - vehicleType = VehicleInfo.VehicleType.None; + vehicleType = VehicleInfo.VehicleType.None; #endif - } - } + } + } #if VEHICLERESTRICTIONS - private bool CanUseLane(ushort segmentId, NetInfo segmentInfo, int laneIndex, NetInfo.Lane laneInfo) { - if (!Options.vehicleRestrictionsEnabled || - m_queueItem.vehicleType == ExtVehicleType.None || - m_queueItem.vehicleType == ExtVehicleType.Tram || - (laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) { - return true; - } + private bool CanUseLane(ushort segmentId, NetInfo segmentInfo, int laneIndex, NetInfo.Lane laneInfo) { + if (!Options.vehicleRestrictionsEnabled || + m_queueItem.vehicleType == ExtVehicleType.None || + m_queueItem.vehicleType == ExtVehicleType.Tram || + (laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) { + return true; + } - ExtVehicleType allowedTypes = m_vehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)laneIndex, laneInfo, VehicleRestrictionsMode.Configured); + ExtVehicleType allowedTypes = m_vehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - return ((allowedTypes & m_queueItem.vehicleType) != ExtVehicleType.None); - } + return ((allowedTypes & m_queueItem.vehicleType) != ExtVehicleType.None); + } #endif #if ADVANCEDAI && ROUTING - private void CalculateAdvancedAiCostFactors( -#if DEBUG - bool debug, uint unit, -#endif - ref BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, ushort nextNodeId, ref NetNode nextNode, ref float segmentSelectionCost, ref float laneSelectionCost, ref float laneChangingCost - ) { -#if DEBUG - if (debug) { - Debug(unit, item, $"CalculateAdvancedAiCostFactors called.\n" + - "\t" + $"nextNodeId={nextNodeId}\n" + - "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + - "\t" + $"laneSelectionCost={laneSelectionCost}\n" + - "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - - NetInfo prevSegmentInfo = prevSegment.Info; - bool nextIsJunction = (nextNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; - - if (nextIsJunction) { - /* - * ======================================================================================================= - * Calculate costs for randomized lane selection behind junctions and highway transitions - * ======================================================================================================= - */ - // TODO check if highway transitions are actually covered by this code - if ( - !m_isHeavyVehicle && - m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel > 0 && - m_pathRandomizer.Int32(m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel) == 0 && - m_pathRandomizer.Int32((uint)prevSegmentInfo.m_lanes.Length) == 0 - ) { - // randomized lane selection at junctions - laneSelectionCost *= 1f + m_conf.AdvancedVehicleAI.LaneRandomizationCostFactor; - -#if DEBUG - if (debug) { - Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated randomized lane selection costs\n" + - "\t" + $"laneSelectionCost={laneSelectionCost}" - ); - } -#endif - } - - /* - * ======================================================================================================= - * Calculate junction costs - * ======================================================================================================= - */ - // TODO if (prevSegmentRouting.highway) ? - segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.JunctionBaseCost; - -#if DEBUG - if (debug) { - Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated junction costs\n" + - "\t" + $"segmentSelectionCost={segmentSelectionCost}" - ); - } -#endif - } - - bool nextIsStartNode = prevSegment.m_startNode == nextNodeId; - bool nextIsEndNode = nextNodeId == prevSegment.m_endNode; - if (nextIsStartNode || nextIsEndNode) { // next node is a regular node - /* - * ======================================================================================================= - * Calculate traffic measurement costs for segment selection - * ======================================================================================================= - */ - NetInfo.Direction prevFinalDir = nextIsStartNode ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; - prevFinalDir = ((prevSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? prevFinalDir : NetInfo.InvertDirection(prevFinalDir); - - float segmentTraffic = Mathf.Clamp(1f - (float)m_trafficMeasurementManager.SegmentDirTrafficData[m_trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)].meanSpeed / (float)TrafficMeasurementManager.REF_REL_SPEED + item.m_trafficRand, 0, 1f); - - segmentSelectionCost *= 1f + - m_conf.AdvancedVehicleAI.TrafficCostFactor * - segmentTraffic; - - if ( - m_conf.AdvancedVehicleAI.LaneDensityRandInterval > 0 && - nextIsJunction && - (nextNode.m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut) - ) { - item.m_trafficRand = 0.01f * ((float)m_pathRandomizer.Int32((uint)m_conf.AdvancedVehicleAI.LaneDensityRandInterval + 1u) - m_conf.AdvancedVehicleAI.LaneDensityRandInterval / 2f); - } - - if ( - m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost > 0 && - (Singleton.instance.m_nodes.m_buffer[nextIsStartNode ? prevSegment.m_endNode : prevSegment.m_startNode].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction // check previous node - ) { - /* - * ======================================================================================================= - * Calculate lane changing base cost factor when in front of junctions - * ======================================================================================================= - */ - laneChangingCost *= m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost; - -#if DEBUG - if (debug) { - Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated in-front-of-junction lane changing costs\n" + - "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - } - - /* - * ======================================================================================================= - * Calculate general lane changing base cost factor - * ======================================================================================================= - */ - if ( - m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost > 0 && - m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost > m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost - ) { - float rand = (float)m_pathRandomizer.Int32(101u) / 100f; - laneChangingCost *= m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost + rand * (m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost - m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost); - -#if DEBUG - if (debug) { - Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated base lane changing costs\n" + - "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - } - } - -#if DEBUG - if (debug) { - Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated cost factors\n" + - "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + - "\t" + $"laneSelectionCost={laneSelectionCost}\n" + - "\t" + $"laneChangingCost={laneChangingCost}" - ); - } -#endif - } -#endif - - private void PathFindThread() { - while (true) { - try { - Monitor.Enter(m_queueLock); - - while (m_queueFirst == 0 && !m_terminated) { - Monitor.Wait(m_queueLock); - } - - if (m_terminated) { - break; - } - - m_calculating = m_queueFirst; - // NON-STOCK CODE START - m_queueFirst = CustomPathManager._instance.queueItems[m_calculating].nextPathUnitId; - // NON-STOCK CODE END - // QueueFirst = PathUnits.m_buffer[Calculating].m_nextPathUnit; // stock code commented - - if (m_queueFirst == 0) { - m_queueLast = 0u; - m_queuedPathFindCount = 0; - } else { - m_queuedPathFindCount--; - } - - // NON-STOCK CODE START - CustomPathManager._instance.queueItems[m_calculating].nextPathUnitId = 0u; - // NON-STOCK CODE END - // PathUnits.m_buffer[Calculating].m_nextPathUnit = 0u; // stock code commented - - m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)((m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & ~PathUnit.FLAG_CREATED) | PathUnit.FLAG_CALCULATING); - - // NON-STOCK CODE START - m_queueItem = CustomPathManager._instance.queueItems[m_calculating]; - // NON-STOCK CODE END - } finally { - Monitor.Exit(m_queueLock); - } - - try { - m_pathfindProfiler.BeginStep(); - try { - PathFindImplementation(m_calculating, ref m_pathUnits.m_buffer[m_calculating]); - } finally { - m_pathfindProfiler.EndStep(); - } - } catch (Exception ex) { - UIView.ForwardException(ex); - CODebugBase.Error(LogChannel.Core, "Path find error: " + ex.Message + "\n" + ex.StackTrace); - m_pathUnits.m_buffer[m_calculating].m_pathFindFlags |= PathUnit.FLAG_FAILED; - // NON-STOCK CODE START -#if DEBUG - ++m_failedPathFinds; -#endif - // NON-STOCK CODE END - } - - try { - Monitor.Enter(m_queueLock); - - m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)(m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & ~PathUnit.FLAG_CALCULATING); - - // NON-STOCK CODE START - try { - Monitor.Enter(m_bufferLock); - CustomPathManager._instance.queueItems[m_calculating].queued = false; - CustomPathManager._instance.ReleasePath(m_calculating); - } finally { - Monitor.Exit(m_bufferLock); - } - // NON-STOCK CODE END - // Singleton.instance.ReleasePath(Calculating); // stock code commented - - m_calculating = 0u; - Monitor.Pulse(m_queueLock); - } finally { - Monitor.Exit(m_queueLock); - } - } - } - } -} + private void CalculateAdvancedAiCostFactors( +#if DEBUG + bool debug, uint unit, +#endif + ref BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, ushort nextNodeId, ref NetNode nextNode, ref float segmentSelectionCost, ref float laneSelectionCost, ref float laneChangingCost + ) { +#if DEBUG + if (debug) { + Debug(unit, item, $"CalculateAdvancedAiCostFactors called.\n" + + "\t" + $"nextNodeId={nextNodeId}\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + + NetInfo prevSegmentInfo = prevSegment.Info; + bool nextIsJunction = (nextNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; + + if (nextIsJunction) { + /* + * ======================================================================================================= + * Calculate costs for randomized lane selection behind junctions and highway transitions + * ======================================================================================================= + */ + // TODO check if highway transitions are actually covered by this code + if ( + !m_isHeavyVehicle && + m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel > 0 && + m_pathRandomizer.Int32(m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel) == 0 && + m_pathRandomizer.Int32((uint)prevSegmentInfo.m_lanes.Length) == 0 + ) { + // randomized lane selection at junctions + laneSelectionCost *= 1f + m_conf.AdvancedVehicleAI.LaneRandomizationCostFactor; + +#if DEBUG + if (debug) { + Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated randomized lane selection costs\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}" + ); + } +#endif + } + + /* + * ======================================================================================================= + * Calculate junction costs + * ======================================================================================================= + */ + // TODO if (prevSegmentRouting.highway) ? + segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.JunctionBaseCost; + +#if DEBUG + if (debug) { + Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated junction costs\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}" + ); + } +#endif + } + + bool nextIsStartNode = prevSegment.m_startNode == nextNodeId; + bool nextIsEndNode = nextNodeId == prevSegment.m_endNode; + if (nextIsStartNode || nextIsEndNode) { // next node is a regular node + /* + * ======================================================================================================= + * Calculate traffic measurement costs for segment selection + * ======================================================================================================= + */ + NetInfo.Direction prevFinalDir = nextIsStartNode ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; + prevFinalDir = ((prevSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? prevFinalDir : NetInfo.InvertDirection(prevFinalDir); + + float segmentTraffic = Mathf.Clamp(1f - (float)m_trafficMeasurementManager.SegmentDirTrafficData[m_trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)].meanSpeed / (float)TrafficMeasurementManager.REF_REL_SPEED + item.m_trafficRand, 0, 1f); + + segmentSelectionCost *= 1f + + m_conf.AdvancedVehicleAI.TrafficCostFactor * + segmentTraffic; + + if ( + m_conf.AdvancedVehicleAI.LaneDensityRandInterval > 0 && + nextIsJunction && + (nextNode.m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut) + ) { + item.m_trafficRand = 0.01f * ((float)m_pathRandomizer.Int32((uint)m_conf.AdvancedVehicleAI.LaneDensityRandInterval + 1u) - m_conf.AdvancedVehicleAI.LaneDensityRandInterval / 2f); + } + + if ( + m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost > 0 && + (Singleton.instance.m_nodes.m_buffer[nextIsStartNode ? prevSegment.m_endNode : prevSegment.m_startNode].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction // check previous node + ) { + /* + * ======================================================================================================= + * Calculate lane changing base cost factor when in front of junctions + * ======================================================================================================= + */ + laneChangingCost *= m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost; + +#if DEBUG + if (debug) { + Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated in-front-of-junction lane changing costs\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + } + + /* + * ======================================================================================================= + * Calculate general lane changing base cost factor + * ======================================================================================================= + */ + if ( + m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost > 0 && + m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost > m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost + ) { + float rand = (float)m_pathRandomizer.Int32(101u) / 100f; + laneChangingCost *= m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost + rand * (m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost - m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost); + +#if DEBUG + if (debug) { + Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated base lane changing costs\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + } + } + +#if DEBUG + if (debug) { + Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated cost factors\n" + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + + "\t" + $"laneChangingCost={laneChangingCost}" + ); + } +#endif + } +#endif + + private void PathFindThread() { + while (true) { + try { + Monitor.Enter(m_queueLock); + + while (m_queueFirst == 0 && !m_terminated) { + Monitor.Wait(m_queueLock); + } + + if (m_terminated) { + break; + } + + m_calculating = m_queueFirst; + // NON-STOCK CODE START + m_queueFirst = CustomPathManager._instance.queueItems[m_calculating].nextPathUnitId; + // NON-STOCK CODE END + // QueueFirst = PathUnits.m_buffer[Calculating].m_nextPathUnit; // stock code commented + + if (m_queueFirst == 0) { + m_queueLast = 0u; + m_queuedPathFindCount = 0; + } else { + m_queuedPathFindCount--; + } + + // NON-STOCK CODE START + CustomPathManager._instance.queueItems[m_calculating].nextPathUnitId = 0u; + // NON-STOCK CODE END + // PathUnits.m_buffer[Calculating].m_nextPathUnit = 0u; // stock code commented + + m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)((m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & ~PathUnit.FLAG_CREATED) | PathUnit.FLAG_CALCULATING); + + // NON-STOCK CODE START + m_queueItem = CustomPathManager._instance.queueItems[m_calculating]; + // NON-STOCK CODE END + } finally { + Monitor.Exit(m_queueLock); + } + + try { + m_pathfindProfiler.BeginStep(); + try { + PathFindImplementation(m_calculating, ref m_pathUnits.m_buffer[m_calculating]); + } finally { + m_pathfindProfiler.EndStep(); + } + } catch (Exception ex) { + UIView.ForwardException(ex); + CODebugBase.Error(LogChannel.Core, "Path find error: " + ex.Message + "\n" + ex.StackTrace); + m_pathUnits.m_buffer[m_calculating].m_pathFindFlags |= PathUnit.FLAG_FAILED; + // NON-STOCK CODE START +#if DEBUG + ++m_failedPathFinds; +#endif + // NON-STOCK CODE END + } + + try { + Monitor.Enter(m_queueLock); + + m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)(m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & ~PathUnit.FLAG_CALCULATING); + + // NON-STOCK CODE START + try { + Monitor.Enter(m_bufferLock); + CustomPathManager._instance.queueItems[m_calculating].queued = false; + CustomPathManager._instance.ReleasePath(m_calculating); + } finally { + Monitor.Exit(m_bufferLock); + } + // NON-STOCK CODE END + // Singleton.instance.ReleasePath(Calculating); // stock code commented + + m_calculating = 0u; + Monitor.Pulse(m_queueLock); + } finally { + Monitor.Exit(m_queueLock); + } + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/PathFinding/CustomPathManager.cs b/TLM/TLM/Custom/PathFinding/CustomPathManager.cs index 0b1c8fc42..7089a3c96 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathManager.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathManager.cs @@ -22,6 +22,8 @@ // ReSharper disable InconsistentNaming namespace TrafficManager.Custom.PathFinding { + using API.Traffic.Data; + [TargetType(typeof(PathManager))] public class CustomPathManager : PathManager { /// diff --git a/TLM/TLM/Manager/AbstractGeometryObservingManager.cs b/TLM/TLM/Manager/AbstractGeometryObservingManager.cs index 3bf8768e2..5e08db5bf 100644 --- a/TLM/TLM/Manager/AbstractGeometryObservingManager.cs +++ b/TLM/TLM/Manager/AbstractGeometryObservingManager.cs @@ -1,120 +1,115 @@ -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using TrafficManager.Geometry; -using TrafficManager.Geometry.Impl; -using TrafficManager.State; -using TrafficManager.Traffic.Data; -using TrafficManager.Util; +namespace TrafficManager.Manager { + using System; + using CSUtil.Commons; + using Geometry; + using State; + using Traffic.Data; + using Util; -namespace TrafficManager.Manager { - public abstract class AbstractGeometryObservingManager : AbstractCustomManager, IObserver { - private IDisposable geoUpdateUnsubscriber = null; + public abstract class AbstractGeometryObservingManager : AbstractCustomManager, IObserver { + private IDisposable geoUpdateUnsubscriber = null; - private object geoLock = new object(); + private object geoLock = new object(); - /// - /// Handles an invalid segment - /// - /// segment geometry - protected virtual void HandleInvalidSegment(ref ExtSegment seg) { } + /// + /// Handles an invalid segment + /// + /// segment geometry + protected virtual void HandleInvalidSegment(ref ExtSegment seg) { } - /// - /// Handles a valid segment - /// - /// segment geometry - protected virtual void HandleValidSegment(ref ExtSegment seg) { } + /// + /// Handles a valid segment + /// + /// segment geometry + protected virtual void HandleValidSegment(ref ExtSegment seg) { } - /// - /// Handles an invalid node - /// - /// node geometry - protected virtual void HandleInvalidNode(ushort nodeId, ref NetNode node) { } + /// + /// Handles an invalid node + /// + /// node geometry + protected virtual void HandleInvalidNode(ushort nodeId, ref NetNode node) { } - /// - /// Handles a valid node - /// - /// node geometry - protected virtual void HandleValidNode(ushort nodeId, ref NetNode node) { } + /// + /// Handles a valid node + /// + /// node geometry + protected virtual void HandleValidNode(ushort nodeId, ref NetNode node) { } - /// - /// Handles a segment replacement - /// - /// segment replacement - /// new segment end geometry - protected virtual void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { } + /// + /// Handles a segment replacement + /// + /// segment replacement + /// new segment end geometry + protected virtual void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { } - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - } + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + } - public override void OnLevelLoading() { - base.OnLevelLoading(); - geoUpdateUnsubscriber = Constants.ManagerFactory.GeometryManager.Subscribe(this); - } + public override void OnLevelLoading() { + base.OnLevelLoading(); + geoUpdateUnsubscriber = Constants.ManagerFactory.GeometryManager.Subscribe(this); + } - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - if (geoUpdateUnsubscriber != null) { - geoUpdateUnsubscriber.Dispose(); - } - } + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + if (geoUpdateUnsubscriber != null) { + geoUpdateUnsubscriber.Dispose(); + } + } - public void OnUpdate(GeometryUpdate update) { - if (update.segment != null) { - // Handle a segment update - ExtSegment seg = (ExtSegment)update.segment; - if (!seg.valid) { + public void OnUpdate(GeometryUpdate update) { + if (update.segment != null) { + // Handle a segment update + ExtSegment seg = (ExtSegment)update.segment; + if (!seg.valid) { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleInvalidSegment({seg.segmentId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleInvalidSegment({seg.segmentId})"); #endif - HandleInvalidSegment(ref seg); - } else { + HandleInvalidSegment(ref seg); + } else { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleValidSegment({seg.segmentId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleValidSegment({seg.segmentId})"); #endif - HandleValidSegment(ref seg); - } - } else if (update.nodeId != null) { - // Handle a node update - ushort nodeId = (ushort)update.nodeId; - Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - if ((node.m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) == NetNode.Flags.Created) { + HandleValidSegment(ref seg); + } + } else if (update.nodeId != null) { + // Handle a node update + ushort nodeId = (ushort)update.nodeId; + Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + if ((node.m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) == NetNode.Flags.Created) { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleValidNode({nodeId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleValidNode({nodeId})"); #endif - HandleValidNode(nodeId, ref node); - } else { + HandleValidNode(nodeId, ref node); + } else { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleInvalidNode({nodeId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleInvalidNode({nodeId})"); #endif - HandleInvalidNode(nodeId, ref node); - } - return true; - }); - } else { - // Handle a segment end replacement - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + HandleInvalidNode(nodeId, ref node); + } + return true; + }); + } else { + // Handle a segment end replacement + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleSegmentReplacement({update.replacement.oldSegmentEndId} -> {update.replacement.newSegmentEndId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleSegmentReplacement({update.replacement.oldSegmentEndId} -> {update.replacement.newSegmentEndId})"); #endif - HandleSegmentEndReplacement(update.replacement, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(update.replacement.newSegmentEndId.SegmentId, update.replacement.newSegmentEndId.StartNode)]); - } - } + HandleSegmentEndReplacement(update.replacement, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(update.replacement.newSegmentEndId.SegmentId, update.replacement.newSegmentEndId.StartNode)]); + } + } - ~AbstractGeometryObservingManager() { - if (geoUpdateUnsubscriber != null) { - geoUpdateUnsubscriber.Dispose(); - } - } - } -} + ~AbstractGeometryObservingManager() { + if (geoUpdateUnsubscriber != null) { + geoUpdateUnsubscriber.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs b/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs index 02f7fc279..f548c10dc 100644 --- a/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs +++ b/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs @@ -1,195 +1,191 @@ -using ColossalFramework; -using ColossalFramework.Globalization; -using ColossalFramework.Math; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Custom.AI; -using TrafficManager.Custom.PathFinding; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using TrafficManager.UI; -using TrafficManager.Util; -using UnityEngine; -using static TrafficManager.Traffic.Data.ExtCitizenInstance; - -namespace TrafficManager.Manager.Impl { - public class AdvancedParkingManager : AbstractFeatureManager, IAdvancedParkingManager { - public static AdvancedParkingManager Instance { get; private set; } = null; - - static AdvancedParkingManager() { - Instance = new AdvancedParkingManager(); - } - - protected override void OnDisableFeatureInternal() { - for (int citizenInstanceId = 0; citizenInstanceId < ExtCitizenInstanceManager.Instance.ExtInstances.Length; ++citizenInstanceId) { - ExtPathMode pathMode = ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].pathMode; - switch (pathMode) { - case ExtPathMode.RequiresWalkingPathToParkedCar: - case ExtPathMode.CalculatingWalkingPathToParkedCar: - case ExtPathMode.WalkingToParkedCar: - case ExtPathMode.ApproachingParkedCar: - // citizen requires a path to their parked car: release instance to prevent it from floating - Services.CitizenService.ReleaseCitizenInstance((ushort)citizenInstanceId); - break; - case ExtPathMode.RequiresCarPath: - case ExtPathMode.RequiresMixedCarPathToTarget: - case ExtPathMode.CalculatingCarPathToKnownParkPos: - case ExtPathMode.CalculatingCarPathToTarget: - case ExtPathMode.DrivingToKnownParkPos: - case ExtPathMode.DrivingToTarget: - if (Services.CitizenService.CheckCitizenInstanceFlags((ushort)citizenInstanceId, CitizenInstance.Flags.Character)) { - // citizen instance requires a car but is walking: release instance to prevent it from floating - Services.CitizenService.ReleaseCitizenInstance((ushort)citizenInstanceId); - } - break; - } - } - ExtCitizenManager.Instance.Reset(); - ExtCitizenInstanceManager.Instance.Reset(); - } - - protected override void OnEnableFeatureInternal() { - - } - - public bool EnterParkedCar(ushort instanceID, ref CitizenInstance instanceData, ushort parkedVehicleId, out ushort vehicleId) { -#if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}, ..., {parkedVehicleId}) called."); -#endif - VehicleManager vehManager = Singleton.instance; - NetManager netManager = Singleton.instance; - CitizenManager citManager = Singleton.instance; - - Vector3 parkedVehPos = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; - Quaternion parkedVehRot = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_rotation; - VehicleInfo vehicleInfo = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].Info; - - PathUnit.Position vehLanePathPos; - if (!CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(0, out vehLanePathPos)) { -#if DEBUG - if (debug) - Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not get first car path position of citizen instance {instanceID}!"); -#endif - - vehicleId = 0; - return false; - } - uint vehLaneId = PathManager.GetLaneID(vehLanePathPos); -#if DEBUG - if (fineDebug) - Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Determined vehicle position for citizen instance {instanceID}: seg. {vehLanePathPos.m_segment}, lane {vehLanePathPos.m_lane}, off {vehLanePathPos.m_offset} (lane id {vehLaneId})"); -#endif +namespace TrafficManager.Manager.Impl { + using System; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Globalization; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.AI; + using Custom.PathFinding; + using State; + using Traffic.Data; + using Traffic.Enums; + using UI; + using UnityEngine; + using Util; + + public class AdvancedParkingManager : AbstractFeatureManager, IAdvancedParkingManager { + public static AdvancedParkingManager Instance { get; private set; } = null; + + static AdvancedParkingManager() { + Instance = new AdvancedParkingManager(); + } + + protected override void OnDisableFeatureInternal() { + for (int citizenInstanceId = 0; citizenInstanceId < ExtCitizenInstanceManager.Instance.ExtInstances.Length; ++citizenInstanceId) { + ExtPathMode pathMode = ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].pathMode; + switch (pathMode) { + case ExtPathMode.RequiresWalkingPathToParkedCar: + case ExtPathMode.CalculatingWalkingPathToParkedCar: + case ExtPathMode.WalkingToParkedCar: + case ExtPathMode.ApproachingParkedCar: + // citizen requires a path to their parked car: release instance to prevent it from floating + Services.CitizenService.ReleaseCitizenInstance((ushort)citizenInstanceId); + break; + case ExtPathMode.RequiresCarPath: + case ExtPathMode.RequiresMixedCarPathToTarget: + case ExtPathMode.CalculatingCarPathToKnownParkPos: + case ExtPathMode.CalculatingCarPathToTarget: + case ExtPathMode.DrivingToKnownParkPos: + case ExtPathMode.DrivingToTarget: + if (Services.CitizenService.CheckCitizenInstanceFlags((ushort)citizenInstanceId, CitizenInstance.Flags.Character)) { + // citizen instance requires a car but is walking: release instance to prevent it from floating + Services.CitizenService.ReleaseCitizenInstance((ushort)citizenInstanceId); + } + break; + } + } + ExtCitizenManager.Instance.Reset(); + ExtCitizenInstanceManager.Instance.Reset(); + } + + protected override void OnEnableFeatureInternal() { + + } + + public bool EnterParkedCar(ushort instanceID, ref CitizenInstance instanceData, ushort parkedVehicleId, out ushort vehicleId) { +#if DEBUG + bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (debug) + Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}, ..., {parkedVehicleId}) called."); +#endif + VehicleManager vehManager = Singleton.instance; + NetManager netManager = Singleton.instance; + CitizenManager citManager = Singleton.instance; + + Vector3 parkedVehPos = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; + Quaternion parkedVehRot = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_rotation; + VehicleInfo vehicleInfo = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].Info; + + PathUnit.Position vehLanePathPos; + if (!CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(0, out vehLanePathPos)) { +#if DEBUG + if (debug) + Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not get first car path position of citizen instance {instanceID}!"); +#endif + + vehicleId = 0; + return false; + } + uint vehLaneId = PathManager.GetLaneID(vehLanePathPos); +#if DEBUG + if (fineDebug) + Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Determined vehicle position for citizen instance {instanceID}: seg. {vehLanePathPos.m_segment}, lane {vehLanePathPos.m_lane}, off {vehLanePathPos.m_offset} (lane id {vehLaneId})"); +#endif + + Vector3 vehLanePos; + float vehLaneOff; + netManager.m_lanes.m_buffer[vehLaneId].GetClosestPosition(parkedVehPos, out vehLanePos, out vehLaneOff); + byte vehLaneOffset = (byte)Mathf.Clamp(Mathf.RoundToInt(vehLaneOff * 255f), 0, 255); + + // movement vector from parked vehicle position to road position + Vector3 forwardVector = parkedVehPos + Vector3.ClampMagnitude(vehLanePos - parkedVehPos, 5f); + if (vehManager.CreateVehicle(out vehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkedVehPos, TransferManager.TransferReason.None, false, false)) { + // update frame data + Vehicle.Frame frame = vehManager.m_vehicles.m_buffer[(int)vehicleId].m_frame0; + frame.m_rotation = parkedVehRot; + + vehManager.m_vehicles.m_buffer[vehicleId].m_frame0 = frame; + vehManager.m_vehicles.m_buffer[vehicleId].m_frame1 = frame; + vehManager.m_vehicles.m_buffer[vehicleId].m_frame2 = frame; + vehManager.m_vehicles.m_buffer[vehicleId].m_frame3 = frame; + vehicleInfo.m_vehicleAI.FrameDataUpdated(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId], ref frame); + + // update vehicle target position + vehManager.m_vehicles.m_buffer[vehicleId].m_targetPos0 = new Vector4(vehLanePos.x, vehLanePos.y, vehLanePos.z, 2f); + + // update other fields + vehManager.m_vehicles.m_buffer[vehicleId].m_flags = (vehManager.m_vehicles.m_buffer[vehicleId].m_flags | Vehicle.Flags.Stopped); + vehManager.m_vehicles.m_buffer[vehicleId].m_path = instanceData.m_path; + vehManager.m_vehicles.m_buffer[vehicleId].m_pathPositionIndex = 0; + vehManager.m_vehicles.m_buffer[vehicleId].m_lastPathOffset = vehLaneOffset; + vehManager.m_vehicles.m_buffer[vehicleId].m_transferSize = (ushort)(instanceData.m_citizen & 65535u); + + if (!vehicleInfo.m_vehicleAI.TrySpawn(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId])) { +#if DEBUG + if (debug) + Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not spawn a {vehicleInfo.m_vehicleType} for citizen instance {instanceID}!"); +#endif + return false; + } + + // change instances + InstanceID parkedVehInstance = InstanceID.Empty; + parkedVehInstance.ParkedVehicle = parkedVehicleId; + InstanceID vehInstance = InstanceID.Empty; + vehInstance.Vehicle = vehicleId; + Singleton.instance.ChangeInstance(parkedVehInstance, vehInstance); - Vector3 vehLanePos; - float vehLaneOff; - netManager.m_lanes.m_buffer[vehLaneId].GetClosestPosition(parkedVehPos, out vehLanePos, out vehLaneOff); - byte vehLaneOffset = (byte)Mathf.Clamp(Mathf.RoundToInt(vehLaneOff * 255f), 0, 255); + // set vehicle id for citizen instance + instanceData.m_path = 0u; + citManager.m_citizens.m_buffer[instanceData.m_citizen].SetParkedVehicle(instanceData.m_citizen, 0); + citManager.m_citizens.m_buffer[instanceData.m_citizen].SetVehicle(instanceData.m_citizen, vehicleId, 0u); - // movement vector from parked vehicle position to road position - Vector3 forwardVector = parkedVehPos + Vector3.ClampMagnitude(vehLanePos - parkedVehPos, 5f); - if (vehManager.CreateVehicle(out vehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkedVehPos, TransferManager.TransferReason.None, false, false)) { - // update frame data - Vehicle.Frame frame = vehManager.m_vehicles.m_buffer[(int)vehicleId].m_frame0; - frame.m_rotation = parkedVehRot; + // update citizen instance flags + instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath; + instanceData.m_flags &= ~CitizenInstance.Flags.EnteringVehicle; + instanceData.m_flags &= ~CitizenInstance.Flags.TryingSpawnVehicle; + instanceData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting; + instanceData.m_waitCounter = 0; - vehManager.m_vehicles.m_buffer[vehicleId].m_frame0 = frame; - vehManager.m_vehicles.m_buffer[vehicleId].m_frame1 = frame; - vehManager.m_vehicles.m_buffer[vehicleId].m_frame2 = frame; - vehManager.m_vehicles.m_buffer[vehicleId].m_frame3 = frame; - vehicleInfo.m_vehicleAI.FrameDataUpdated(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId], ref frame); + // unspawn citizen instance + instanceData.Unspawn(instanceID); - // update vehicle target position - vehManager.m_vehicles.m_buffer[vehicleId].m_targetPos0 = new Vector4(vehLanePos.x, vehLanePos.y, vehLanePos.z, 2f); - - // update other fields - vehManager.m_vehicles.m_buffer[vehicleId].m_flags = (vehManager.m_vehicles.m_buffer[vehicleId].m_flags | Vehicle.Flags.Stopped); - vehManager.m_vehicles.m_buffer[vehicleId].m_path = instanceData.m_path; - vehManager.m_vehicles.m_buffer[vehicleId].m_pathPositionIndex = 0; - vehManager.m_vehicles.m_buffer[vehicleId].m_lastPathOffset = vehLaneOffset; - vehManager.m_vehicles.m_buffer[vehicleId].m_transferSize = (ushort)(instanceData.m_citizen & 65535u); - - if (!vehicleInfo.m_vehicleAI.TrySpawn(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId])) { #if DEBUG - if (debug) - Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not spawn a {vehicleInfo.m_vehicleType} for citizen instance {instanceID}!"); + if (fineDebug) + Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Citizen instance {instanceID} is now entering vehicle {vehicleId}. Set vehicle target position to {vehLanePos} (segment={vehLanePathPos.m_segment}, lane={vehLanePathPos.m_lane}, offset={vehLanePathPos.m_offset})"); #endif - return false; - } - - // change instances - InstanceID parkedVehInstance = InstanceID.Empty; - parkedVehInstance.ParkedVehicle = parkedVehicleId; - InstanceID vehInstance = InstanceID.Empty; - vehInstance.Vehicle = vehicleId; - Singleton.instance.ChangeInstance(parkedVehInstance, vehInstance); - - // set vehicle id for citizen instance - instanceData.m_path = 0u; - citManager.m_citizens.m_buffer[instanceData.m_citizen].SetParkedVehicle(instanceData.m_citizen, 0); - citManager.m_citizens.m_buffer[instanceData.m_citizen].SetVehicle(instanceData.m_citizen, vehicleId, 0u); - - // update citizen instance flags - instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath; - instanceData.m_flags &= ~CitizenInstance.Flags.EnteringVehicle; - instanceData.m_flags &= ~CitizenInstance.Flags.TryingSpawnVehicle; - instanceData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting; - instanceData.m_waitCounter = 0; - - // unspawn citizen instance - instanceData.Unspawn(instanceID); + return true; + } else { + // failed to find a road position #if DEBUG - if (fineDebug) - Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Citizen instance {instanceID} is now entering vehicle {vehicleId}. Set vehicle target position to {vehLanePos} (segment={vehLanePathPos.m_segment}, lane={vehLanePathPos.m_lane}, offset={vehLanePathPos.m_offset})"); + if (debug) + Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not find a road position for citizen instance {instanceID} near parked vehicle {parkedVehicleId}!"); #endif + return false; + } + } - return true; - } else { - // failed to find a road position + public ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref CitizenInstance citizenInstance, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizen, ExtPathState mainPathState) { #if DEBUG - if (debug) - Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not find a road position for citizen instance {instanceID} near parked vehicle {parkedVehicleId}!"); + bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == citizenInstanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenInstance.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == citizenInstance.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == citizenInstance.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}) called."); #endif - return false; - } - } - - public ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref CitizenInstance citizenInstance, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizen, ExtPathState mainPathState) { + if (mainPathState == ExtPathState.Calculating) { + // main path is still calculating, do not check return path #if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == citizenInstanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenInstance.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == citizenInstance.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == citizenInstance.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}) called."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); #endif - if (mainPathState == ExtPathState.Calculating) { - // main path is still calculating, do not check return path -#if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); -#endif - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - } + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; // if (!Constants.ManagerFactory.ExtCitizenInstanceManager.IsValid(citizenInstanceId)) { // // no citizen @@ -200,102 +196,102 @@ public ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref Cit // return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); // } - if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { - // main path failed or non-existing + if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { + // main path failed or non-existing #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); #endif - if (mainPathState == ExtPathState.Failed) { + if (mainPathState == ExtPathState.Failed) { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Checking if path-finding may be repeated."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Checking if path-finding may be repeated."); #endif - return OnCitizenPathFindFailure(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen); - } else { + return OnCitizenPathFindFailure(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen); + } else { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Resetting instance and returning FAILED."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Resetting instance and returning FAILED."); #endif - extCitInstMan.Reset(ref extInstance); - return ExtSoftPathState.FailedHard; - } - } + extCitInstMan.Reset(ref extInstance); + return ExtSoftPathState.FailedHard; + } + } - // main path state is READY + // main path state is READY - // main path calculation succeeded: update return path state and check its state if necessary - extCitInstMan.UpdateReturnPathState(ref extInstance); + // main path calculation succeeded: update return path state and check its state if necessary + extCitInstMan.UpdateReturnPathState(ref extInstance); - bool success = true; - switch (extInstance.returnPathState) { - case ExtPathState.None: - default: - // no return path calculated: ignore + bool success = true; + switch (extInstance.returnPathState) { + case ExtPathState.None: + default: + // no return path calculated: ignore #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): return path state is None. Ignoring and returning main path state."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): return path state is None. Ignoring and returning main path state."); #endif - break; - case ExtPathState.Calculating: // OK - // return path not read yet: wait for it + break; + case ExtPathState.Calculating: // OK + // return path not read yet: wait for it #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): return path state is still calculating."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): return path state is still calculating."); #endif - return ExtSoftPathState.Calculating; - case ExtPathState.Failed: // OK - // no walking path from parking position to target found. flag main path as 'failed'. + return ExtSoftPathState.Calculating; + case ExtPathState.Failed: // OK + // no walking path from parking position to target found. flag main path as 'failed'. #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Return path FAILED."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Return path FAILED."); #endif - success = false; - break; - case ExtPathState.Ready: - // handle valid return path + success = false; + break; + case ExtPathState.Ready: + // handle valid return path #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Path is READY."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Path is READY."); #endif - break; - } + break; + } - extCitInstMan.ReleaseReturnPath(ref extInstance); + extCitInstMan.ReleaseReturnPath(ref extInstance); - if (success) { - // handle path find success - return OnCitizenPathFindSuccess(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen, ref citizen); - } else { - // handle path find failure - return OnCitizenPathFindFailure(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen); - } - } + if (success) { + // handle path find success + return OnCitizenPathFindSuccess(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen, ref citizen); + } else { + // handle path find failure + return OnCitizenPathFindFailure(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen); + } + } - public ExtSoftPathState UpdateCarPathState(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ExtPathState mainPathState) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + public ExtSoftPathState UpdateCarPathState(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ExtPathState mainPathState) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; #if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId) && - (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}) called."); + bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId) && + (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}) called."); #endif - if (mainPathState == ExtPathState.Calculating) { - // main path is still calculating, do not check return path + if (mainPathState == ExtPathState.Calculating) { + // main path is still calculating, do not check return path #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); #endif - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - } + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } // if (!driverExtInstance.IsValid()) { // // no driver @@ -306,346 +302,346 @@ public ExtSoftPathState UpdateCarPathState(ushort vehicleId, ref Vehicle vehicle // return mainPathState; // } - //ExtCitizenInstance driverExtInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(CustomPassengerCarAI.GetDriverInstance(vehicleId, ref vehicleData)); + //ExtCitizenInstance driverExtInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(CustomPassengerCarAI.GetDriverInstance(vehicleId, ref vehicleData)); - if (!extCitInstMan.IsValid(driverExtInstance.instanceId)) { - // no driver + if (!extCitInstMan.IsValid(driverExtInstance.instanceId)) { + // no driver #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): no driver found!"); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): no driver found!"); #endif - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - } + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } - if (Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].vehicleType != ExtVehicleType.PassengerCar) { - // non-passenger cars are not handled + if (Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].vehicleType != ExtVehicleType.PassengerCar) { + // non-passenger cars are not handled #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): not a passenger car!"); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): not a passenger car!"); #endif - extCitInstMan.Reset(ref driverExtInstance); - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - } + extCitInstMan.Reset(ref driverExtInstance); + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } - if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { - // main path failed or non-existing: reset return path + if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { + // main path failed or non-existing: reset return path #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); #endif - if (mainPathState == ExtPathState.Failed) { + if (mainPathState == ExtPathState.Failed) { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Checking if path-finding may be repeated."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Checking if path-finding may be repeated."); #endif - extCitInstMan.ReleaseReturnPath(ref driverExtInstance); - return OnCarPathFindFailure(vehicleId, ref vehicleData, ref driverInstance, ref driverExtInstance); - } else { + extCitInstMan.ReleaseReturnPath(ref driverExtInstance); + return OnCarPathFindFailure(vehicleId, ref vehicleData, ref driverInstance, ref driverExtInstance); + } else { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Resetting instance and returning FAILED."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Resetting instance and returning FAILED."); #endif - extCitInstMan.Reset(ref driverExtInstance); - return ExtSoftPathState.FailedHard; - } - } + extCitInstMan.Reset(ref driverExtInstance); + return ExtSoftPathState.FailedHard; + } + } - // main path state is READY + // main path state is READY - // main path calculation succeeded: update return path state and check its state - extCitInstMan.UpdateReturnPathState(ref driverExtInstance); + // main path calculation succeeded: update return path state and check its state + extCitInstMan.UpdateReturnPathState(ref driverExtInstance); - switch (driverExtInstance.returnPathState) { - case ExtPathState.None: - default: - // no return path calculated: ignore + switch (driverExtInstance.returnPathState) { + case ExtPathState.None: + default: + // no return path calculated: ignore #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): return path state is None. Setting pathMode=DrivingToTarget and returning main path state."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): return path state is None. Setting pathMode=DrivingToTarget and returning main path state."); #endif - driverExtInstance.pathMode = ExtPathMode.DrivingToTarget; - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - case ExtPathState.Calculating: - // return path not read yet: wait for it + driverExtInstance.pathMode = ExtPathMode.DrivingToTarget; + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + case ExtPathState.Calculating: + // return path not read yet: wait for it #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): return path state is still calculating."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): return path state is still calculating."); #endif - return ExtSoftPathState.Calculating; - case ExtPathState.Failed: - // no walking path from parking position to target found. flag main path as 'failed'. + return ExtSoftPathState.Calculating; + case ExtPathState.Failed: + // no walking path from parking position to target found. flag main path as 'failed'. #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Return path {driverExtInstance.returnPathId} FAILED. Forcing path-finding to fail."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Return path {driverExtInstance.returnPathId} FAILED. Forcing path-finding to fail."); #endif - extCitInstMan.Reset(ref driverExtInstance); - return ExtSoftPathState.FailedHard; - case ExtPathState.Ready: - // handle valid return path - extCitInstMan.ReleaseReturnPath(ref driverExtInstance); + extCitInstMan.Reset(ref driverExtInstance); + return ExtSoftPathState.FailedHard; + case ExtPathState.Ready: + // handle valid return path + extCitInstMan.ReleaseReturnPath(ref driverExtInstance); #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Path is ready for vehicle {vehicleId}, citizen instance {driverExtInstance.instanceId}! CurrentPathMode={driverExtInstance.pathMode}"); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Path is ready for vehicle {vehicleId}, citizen instance {driverExtInstance.instanceId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif - byte laneTypes = CustomPathManager._instance.m_pathUnits.m_buffer[vehicleData.m_path].m_laneTypes; - bool usesPublicTransport = (laneTypes & (byte)(NetInfo.LaneType.PublicTransport)) != 0; + byte laneTypes = CustomPathManager._instance.m_pathUnits.m_buffer[vehicleData.m_path].m_laneTypes; + bool usesPublicTransport = (laneTypes & (byte)(NetInfo.LaneType.PublicTransport)) != 0; - if (usesPublicTransport && (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos || driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos)) { - driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; - driverExtInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; - driverExtInstance.parkingSpaceLocationId = 0; - } + if (usesPublicTransport && (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos || driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos)) { + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; + driverExtInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; + driverExtInstance.parkingSpaceLocationId = 0; + } - if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { - driverExtInstance.pathMode = ExtPathMode.DrivingToAltParkPos; - driverExtInstance.parkingPathStartPosition = null; + if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { + driverExtInstance.pathMode = ExtPathMode.DrivingToAltParkPos; + driverExtInstance.parkingPathStartPosition = null; #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Path to an alternative parking position is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Path to an alternative parking position is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif - } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget) { - driverExtInstance.pathMode = ExtPathMode.DrivingToTarget; + } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget) { + driverExtInstance.pathMode = ExtPathMode.DrivingToTarget; #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Car path is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Car path is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif - } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos) { - driverExtInstance.pathMode = ExtPathMode.DrivingToKnownParkPos; + } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos) { + driverExtInstance.pathMode = ExtPathMode.DrivingToKnownParkPos; #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Car path to known parking position is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Car path to known parking position is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif - } - return ExtSoftPathState.Ready; - } - } + } + return ExtSoftPathState.Ready; + } + } - public ParkedCarApproachState CitizenApproachingParkedCarSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 physicsLodRefPos, ref VehicleParked parkedCar) { + public ParkedCarApproachState CitizenApproachingParkedCarSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 physicsLodRefPos, ref VehicleParked parkedCar) { #if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif - if ((instanceData.m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) { + if ((instanceData.m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) { #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.CheckCitizenReachedParkedCar({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CheckCitizenReachedParkedCar({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); #endif - return ParkedCarApproachState.None; - } + return ParkedCarApproachState.None; + } - //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); + //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); - if (extInstance.pathMode != ExtPathMode.ApproachingParkedCar && extInstance.pathMode != ExtPathMode.WalkingToParkedCar) { + if (extInstance.pathMode != ExtPathMode.ApproachingParkedCar && extInstance.pathMode != ExtPathMode.WalkingToParkedCar) { #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not reaching a parked car ({extInstance.pathMode})"); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not reaching a parked car ({extInstance.pathMode})"); #endif - return ParkedCarApproachState.None; - } + return ParkedCarApproachState.None; + } - if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) { + if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) { #if DEBUG - /*if (fineDebug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ + /*if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ #endif - return ParkedCarApproachState.None; - } + return ParkedCarApproachState.None; + } - Vector3 lastFramePos = instanceData.GetLastFramePosition(); - Vector3 doorPosition = parkedCar.GetClosestDoorPosition(parkedCar.m_position, VehicleInfo.DoorType.Enter); + Vector3 lastFramePos = instanceData.GetLastFramePosition(); + Vector3 doorPosition = parkedCar.GetClosestDoorPosition(parkedCar.m_position, VehicleInfo.DoorType.Enter); - if (extInstance.pathMode == ExtPathMode.WalkingToParkedCar) { - // check if path is complete - PathUnit.Position pos; - if (instanceData.m_pathPositionIndex != 255 && (instanceData.m_path == 0 || !CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(instanceData.m_pathPositionIndex >> 1, out pos))) { - extInstance.pathMode = ExtPathMode.ApproachingParkedCar; - extInstance.lastDistanceToParkedCar = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; + if (extInstance.pathMode == ExtPathMode.WalkingToParkedCar) { + // check if path is complete + PathUnit.Position pos; + if (instanceData.m_pathPositionIndex != 255 && (instanceData.m_path == 0 || !CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(instanceData.m_pathPositionIndex >> 1, out pos))) { + extInstance.pathMode = ExtPathMode.ApproachingParkedCar; + extInstance.lastDistanceToParkedCar = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} was walking to parked car and reached final path position. Switched PathMode to {extInstance.pathMode}."); + if (debug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} was walking to parked car and reached final path position. Switched PathMode to {extInstance.pathMode}."); #endif - } - } + } + } - if (extInstance.pathMode == ExtPathMode.ApproachingParkedCar) { - Vector3 doorTargetDir = doorPosition - lastFramePos; - Vector3 doorWalkVector = doorPosition; - float doorTargetDirMagnitude = doorTargetDir.magnitude; - if (doorTargetDirMagnitude > 1f) { - float speed = Mathf.Max(doorTargetDirMagnitude - 5f, doorTargetDirMagnitude * 0.5f); - doorWalkVector = lastFramePos + (doorTargetDir * (speed / doorTargetDirMagnitude)); - } - instanceData.m_targetPos = new Vector4(doorWalkVector.x, doorWalkVector.y, doorWalkVector.z, 0.5f); - instanceData.m_targetDir = VectorUtils.XZ(doorTargetDir); + if (extInstance.pathMode == ExtPathMode.ApproachingParkedCar) { + Vector3 doorTargetDir = doorPosition - lastFramePos; + Vector3 doorWalkVector = doorPosition; + float doorTargetDirMagnitude = doorTargetDir.magnitude; + if (doorTargetDirMagnitude > 1f) { + float speed = Mathf.Max(doorTargetDirMagnitude - 5f, doorTargetDirMagnitude * 0.5f); + doorWalkVector = lastFramePos + (doorTargetDir * (speed / doorTargetDirMagnitude)); + } + instanceData.m_targetPos = new Vector4(doorWalkVector.x, doorWalkVector.y, doorWalkVector.z, 0.5f); + instanceData.m_targetDir = VectorUtils.XZ(doorTargetDir); - CitizenApproachingParkedCarSimulationStep(instanceId, ref instanceData, physicsLodRefPos); + CitizenApproachingParkedCarSimulationStep(instanceId, ref instanceData, physicsLodRefPos); - float doorSqrDist = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; - if (doorSqrDist > GlobalConfig.Instance.ParkingAI.MaxParkedCarInstanceSwitchSqrDistance) { - // citizen is still too far away from the parked car - ExtPathMode oldPathMode = extInstance.pathMode; - if (doorSqrDist > extInstance.lastDistanceToParkedCar + 1024f) { - // distance has increased dramatically since the last time + float doorSqrDist = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; + if (doorSqrDist > GlobalConfig.Instance.ParkingAI.MaxParkedCarInstanceSwitchSqrDistance) { + // citizen is still too far away from the parked car + ExtPathMode oldPathMode = extInstance.pathMode; + if (doorSqrDist > extInstance.lastDistanceToParkedCar + 1024f) { + // distance has increased dramatically since the last time #if DEBUG - if (debug) - Log.Warning($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} is currently reaching their parked car but distance increased! dist={doorSqrDist}, LastDistanceToParkedCar={extInstance.lastDistanceToParkedCar}."); + if (debug) + Log.Warning($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} is currently reaching their parked car but distance increased! dist={doorSqrDist}, LastDistanceToParkedCar={extInstance.lastDistanceToParkedCar}."); - if (GlobalConfig.Instance.Debug.Switches[6]) { - Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): FORCED PAUSE. Distance increased! Citizen instance {instanceId}. dist={doorSqrDist}"); - Singleton.instance.SimulationPaused = true; - } + if (GlobalConfig.Instance.Debug.Switches[6]) { + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): FORCED PAUSE. Distance increased! Citizen instance {instanceId}. dist={doorSqrDist}"); + Singleton.instance.SimulationPaused = true; + } #endif - CitizenInstance.Frame frameData = instanceData.GetLastFrameData(); - frameData.m_position = doorPosition; - instanceData.SetLastFrameData(frameData); + CitizenInstance.Frame frameData = instanceData.GetLastFrameData(); + frameData.m_position = doorPosition; + instanceData.SetLastFrameData(frameData); - extInstance.pathMode = ExtPathMode.RequiresCarPath; + extInstance.pathMode = ExtPathMode.RequiresCarPath; - return ParkedCarApproachState.Approached; - } else if (doorSqrDist < extInstance.lastDistanceToParkedCar) { - extInstance.lastDistanceToParkedCar = doorSqrDist; - } + return ParkedCarApproachState.Approached; + } else if (doorSqrDist < extInstance.lastDistanceToParkedCar) { + extInstance.lastDistanceToParkedCar = doorSqrDist; + } #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} is currently reaching their parked car (dist={doorSqrDist}, LastDistanceToParkedCar={extInstance.lastDistanceToParkedCar}). CurrentDepartureMode={extInstance.pathMode}"); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} is currently reaching their parked car (dist={doorSqrDist}, LastDistanceToParkedCar={extInstance.lastDistanceToParkedCar}). CurrentDepartureMode={extInstance.pathMode}"); #endif - return ParkedCarApproachState.Approaching; - } else { - extInstance.pathMode = ExtPathMode.RequiresCarPath; + return ParkedCarApproachState.Approaching; + } else { + extInstance.pathMode = ExtPathMode.RequiresCarPath; #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} reached parking position (dist={doorSqrDist}). Calculating remaining path now. CurrentDepartureMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} reached parking position (dist={doorSqrDist}). Calculating remaining path now. CurrentDepartureMode={extInstance.pathMode}"); #endif - return ParkedCarApproachState.Approached; - } - } + return ParkedCarApproachState.Approached; + } + } - return ParkedCarApproachState.None; - } - - protected void CitizenApproachingParkedCarSimulationStep(ushort instanceID, ref CitizenInstance instanceData, Vector3 physicsLodRefPos) { - if ((instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { - CitizenInstance.Frame lastFrameData = instanceData.GetLastFrameData(); - int oldGridX = Mathf.Clamp((int)(lastFrameData.m_position.x / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); - int oldGridY = Mathf.Clamp((int)(lastFrameData.m_position.z / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); - bool lodPhysics = Vector3.SqrMagnitude(physicsLodRefPos - lastFrameData.m_position) >= 62500f; - CitizenApproachingParkedCarSimulationStep(instanceID, ref instanceData, ref lastFrameData, lodPhysics); - int newGridX = Mathf.Clamp((int)(lastFrameData.m_position.x / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); - int newGridY = Mathf.Clamp((int)(lastFrameData.m_position.z / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); - if ((newGridX != oldGridX || newGridY != oldGridY) && (instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { - Singleton.instance.RemoveFromGrid(instanceID, ref instanceData, oldGridX, oldGridY); - Singleton.instance.AddToGrid(instanceID, ref instanceData, newGridX, newGridY); - } - if (instanceData.m_flags != CitizenInstance.Flags.None) { - instanceData.SetFrameData(Singleton.instance.m_currentFrameIndex, lastFrameData); - } - } - } + return ParkedCarApproachState.None; + } - protected void CitizenApproachingParkedCarSimulationStep(ushort instanceID, ref CitizenInstance instanceData, ref CitizenInstance.Frame frameData, bool lodPhysics) { - frameData.m_position = frameData.m_position + (frameData.m_velocity * 0.5f); + protected void CitizenApproachingParkedCarSimulationStep(ushort instanceID, ref CitizenInstance instanceData, Vector3 physicsLodRefPos) { + if ((instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { + CitizenInstance.Frame lastFrameData = instanceData.GetLastFrameData(); + int oldGridX = Mathf.Clamp((int)(lastFrameData.m_position.x / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); + int oldGridY = Mathf.Clamp((int)(lastFrameData.m_position.z / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); + bool lodPhysics = Vector3.SqrMagnitude(physicsLodRefPos - lastFrameData.m_position) >= 62500f; + CitizenApproachingParkedCarSimulationStep(instanceID, ref instanceData, ref lastFrameData, lodPhysics); + int newGridX = Mathf.Clamp((int)(lastFrameData.m_position.x / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); + int newGridY = Mathf.Clamp((int)(lastFrameData.m_position.z / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); + if ((newGridX != oldGridX || newGridY != oldGridY) && (instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { + Singleton.instance.RemoveFromGrid(instanceID, ref instanceData, oldGridX, oldGridY); + Singleton.instance.AddToGrid(instanceID, ref instanceData, newGridX, newGridY); + } + if (instanceData.m_flags != CitizenInstance.Flags.None) { + instanceData.SetFrameData(Singleton.instance.m_currentFrameIndex, lastFrameData); + } + } + } - Vector3 targetDiff = (Vector3)instanceData.m_targetPos - frameData.m_position; - Vector3 targetVelDiff = targetDiff - frameData.m_velocity; - float targetVelDiffMag = targetVelDiff.magnitude; + protected void CitizenApproachingParkedCarSimulationStep(ushort instanceID, ref CitizenInstance instanceData, ref CitizenInstance.Frame frameData, bool lodPhysics) { + frameData.m_position = frameData.m_position + (frameData.m_velocity * 0.5f); - targetVelDiff = targetVelDiff * (2f / Mathf.Max(targetVelDiffMag, 2f)); - frameData.m_velocity = frameData.m_velocity + targetVelDiff; - frameData.m_velocity = frameData.m_velocity - (Mathf.Max(0f, Vector3.Dot((frameData.m_position + frameData.m_velocity) - (Vector3)instanceData.m_targetPos, frameData.m_velocity)) / Mathf.Max(0.01f, frameData.m_velocity.sqrMagnitude) * frameData.m_velocity); - if (frameData.m_velocity.sqrMagnitude > 0.01f) { - frameData.m_rotation = Quaternion.LookRotation(frameData.m_velocity); - } - } + Vector3 targetDiff = (Vector3)instanceData.m_targetPos - frameData.m_position; + Vector3 targetVelDiff = targetDiff - frameData.m_velocity; + float targetVelDiffMag = targetVelDiff.magnitude; + + targetVelDiff = targetVelDiff * (2f / Mathf.Max(targetVelDiffMag, 2f)); + frameData.m_velocity = frameData.m_velocity + targetVelDiff; + frameData.m_velocity = frameData.m_velocity - (Mathf.Max(0f, Vector3.Dot((frameData.m_position + frameData.m_velocity) - (Vector3)instanceData.m_targetPos, frameData.m_velocity)) / Mathf.Max(0.01f, frameData.m_velocity.sqrMagnitude) * frameData.m_velocity); + if (frameData.m_velocity.sqrMagnitude > 0.01f) { + frameData.m_rotation = Quaternion.LookRotation(frameData.m_velocity); + } + } - public bool CitizenApproachingTargetSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + public bool CitizenApproachingTargetSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; #if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif - if ((instanceData.m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) { + if ((instanceData.m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) { #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); #endif - return false; - } + return false; + } - //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); + //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); - if (extInstance.pathMode != ExtPathMode.WalkingToTarget && - extInstance.pathMode != ExtPathMode.TaxiToTarget) { + if (extInstance.pathMode != ExtPathMode.WalkingToTarget && + extInstance.pathMode != ExtPathMode.TaxiToTarget) { #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not reaching target ({extInstance.pathMode})"); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not reaching target ({extInstance.pathMode})"); #endif - return false; - } + return false; + } - if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) { + if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) { #if DEBUG - /*if (fineDebug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ + /*if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ #endif - return false; - } + return false; + } - // check if path is complete - PathUnit.Position pos; - if (instanceData.m_pathPositionIndex != 255 && (instanceData.m_path == 0 || !CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(instanceData.m_pathPositionIndex >> 1, out pos))) { - extCitInstMan.Reset(ref extInstance); + // check if path is complete + PathUnit.Position pos; + if (instanceData.m_pathPositionIndex != 255 && (instanceData.m_path == 0 || !CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(instanceData.m_pathPositionIndex >> 1, out pos))) { + extCitInstMan.Reset(ref extInstance); #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): Citizen instance {instanceId} reached target. CurrentDepartureMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): Citizen instance {instanceId} reached target. CurrentDepartureMode={extInstance.pathMode}"); #endif - return true; - } + return true; + } + + return false; + } - return false; - } - - /// - /// Handles a path-finding success for activated Parking AI. - /// - /// Citizen instance id - /// Citizen instance data - /// Extended citizen instance data - /// Extended citizen data - /// Citizen data - /// soft path state - protected ExtSoftPathState OnCitizenPathFindSuccess(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizenData) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; -#if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path-finding succeeded for citizen instance {instanceId}. Path: {instanceData.m_path} vehicle={citizenData.m_vehicle}"); + /// + /// Handles a path-finding success for activated Parking AI. + /// + /// Citizen instance id + /// Citizen instance data + /// Extended citizen instance data + /// Extended citizen data + /// Citizen data + /// soft path state + protected ExtSoftPathState OnCitizenPathFindSuccess(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizenData) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; +#if DEBUG + bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path-finding succeeded for citizen instance {instanceId}. Path: {instanceData.m_path} vehicle={citizenData.m_vehicle}"); #endif // if (!extInstance.IsValid()) { @@ -656,1308 +652,1308 @@ protected ExtSoftPathState OnCitizenPathFindSuccess(ushort instanceId, ref Citiz // return ExtSoftPathState.FailedHard; // } - if (citizenData.m_vehicle == 0) { - // citizen does not already have a vehicle assigned + if (citizenData.m_vehicle == 0) { + // citizen does not already have a vehicle assigned - if (extInstance.pathMode == ExtPathMode.TaxiToTarget) { + if (extInstance.pathMode == ExtPathMode.TaxiToTarget) { #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen uses a taxi. Decreasing public transport demand and returning READY."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen uses a taxi. Decreasing public transport demand and returning READY."); #endif - // cim uses taxi - if (instanceData.m_sourceBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); - } + // cim uses taxi + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); + } - if (instanceData.m_targetBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); - } + if (instanceData.m_targetBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); + } - extCitizen.transportMode |= ExtTransportMode.PublicTransport; - return ExtSoftPathState.Ready; - } + extCitizen.transportMode |= ExtTransportMode.PublicTransport; + return ExtSoftPathState.Ready; + } - ushort parkedVehicleId = citizenData.m_parkedVehicle; - float sqrDistToParkedVehicle = 0f; - if (parkedVehicleId != 0) { - // calculate distance to parked vehicle - VehicleManager vehicleManager = Singleton.instance; - Vector3 doorPosition = vehicleManager.m_parkedVehicles.m_buffer[parkedVehicleId].GetClosestDoorPosition(vehicleManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_position, VehicleInfo.DoorType.Enter); - sqrDistToParkedVehicle = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; - } + ushort parkedVehicleId = citizenData.m_parkedVehicle; + float sqrDistToParkedVehicle = 0f; + if (parkedVehicleId != 0) { + // calculate distance to parked vehicle + VehicleManager vehicleManager = Singleton.instance; + Vector3 doorPosition = vehicleManager.m_parkedVehicles.m_buffer[parkedVehicleId].GetClosestDoorPosition(vehicleManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_position, VehicleInfo.DoorType.Enter); + sqrDistToParkedVehicle = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; + } - byte laneTypes = CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].m_laneTypes; - ushort vehicleTypes = CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].m_vehicleTypes; - bool usesPublicTransport = (laneTypes & (byte)(NetInfo.LaneType.PublicTransport)) != 0; - bool usesCar = (laneTypes & (byte)(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0 && (vehicleTypes & (ushort)(VehicleInfo.VehicleType.Car)) != 0; + byte laneTypes = CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].m_laneTypes; + ushort vehicleTypes = CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].m_vehicleTypes; + bool usesPublicTransport = (laneTypes & (byte)(NetInfo.LaneType.PublicTransport)) != 0; + bool usesCar = (laneTypes & (byte)(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0 && (vehicleTypes & (ushort)(VehicleInfo.VehicleType.Car)) != 0; - if (usesPublicTransport && usesCar && (extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos || extInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos)) { - /* - * when using public transport together with a car (assuming a "source -> walk -> drive -> walk -> use public transport -> walk -> target" path) - * discard parking space information since the cim has to park near the public transport stop - * (instead of parking in the vicinity of the target building). - * - * TODO we could check if the path looks like "source -> walk -> use public transport -> walk -> drive -> [walk ->] target" (in this case parking space information would still be valid) - */ + if (usesPublicTransport && usesCar && (extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos || extInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos)) { + /* + * when using public transport together with a car (assuming a "source -> walk -> drive -> walk -> use public transport -> walk -> target" path) + * discard parking space information since the cim has to park near the public transport stop + * (instead of parking in the vicinity of the target building). + * + * TODO we could check if the path looks like "source -> walk -> use public transport -> walk -> drive -> [walk ->] target" (in this case parking space information would still be valid) + */ #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen uses their car together with public transport. Discarding parking space information and setting path mode to CalculatingCarPathToTarget."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen uses their car together with public transport. Discarding parking space information and setting path mode to CalculatingCarPathToTarget."); #endif - extInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; - extInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; - extInstance.parkingSpaceLocationId = 0; - } + extInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; + extInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; + extInstance.parkingSpaceLocationId = 0; + } - switch (extInstance.pathMode) { - case ExtPathMode.None: // citizen starts at source building - default: - if (extInstance.pathMode != ExtPathMode.None) { + switch (extInstance.pathMode) { + case ExtPathMode.None: // citizen starts at source building + default: + if (extInstance.pathMode != ExtPathMode.None) { #if DEBUG - if (debug) - Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Unexpected path mode {extInstance.pathMode}! {extInstance}"); + if (debug) + Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Unexpected path mode {extInstance.pathMode}! {extInstance}"); #endif - } + } - if (usesCar) { + if (usesCar) { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path for citizen instance {instanceId} contains passenger car section. Ensuring that citizen is allowed to use their car."); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path for citizen instance {instanceId} contains passenger car section. Ensuring that citizen is allowed to use their car."); #endif - ushort sourceBuildingId = instanceData.m_sourceBuilding; - ushort homeId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_homeBuilding; + ushort sourceBuildingId = instanceData.m_sourceBuilding; + ushort homeId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_homeBuilding; - if (parkedVehicleId == 0) { + if (parkedVehicleId == 0) { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId} does not have a parked vehicle! CurrentPathMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId} does not have a parked vehicle! CurrentPathMode={extInstance.pathMode}"); #endif - // try to spawn parked vehicle in the vicinity of the starting point. - VehicleInfo vehicleInfo = null; - if (instanceData.Info.m_agePhase > Citizen.AgePhase.Child) { - // get a random car info (due to the fact we are using a different randomizer, car assignment differs from the selection in ResidentAI.GetVehicleInfo/TouristAI.GetVehicleInfo method, but this should not matter since we are reusing parked vehicle infos there) - vehicleInfo = Singleton.instance.GetRandomVehicleInfo(ref Singleton.instance.m_randomizer, ItemClass.Service.Residential, ItemClass.SubService.ResidentialLow, ItemClass.Level.Level1); - } + // try to spawn parked vehicle in the vicinity of the starting point. + VehicleInfo vehicleInfo = null; + if (instanceData.Info.m_agePhase > Citizen.AgePhase.Child) { + // get a random car info (due to the fact we are using a different randomizer, car assignment differs from the selection in ResidentAI.GetVehicleInfo/TouristAI.GetVehicleInfo method, but this should not matter since we are reusing parked vehicle infos there) + vehicleInfo = Singleton.instance.GetRandomVehicleInfo(ref Singleton.instance.m_randomizer, ItemClass.Service.Residential, ItemClass.SubService.ResidentialLow, ItemClass.Level.Level1); + } - if (vehicleInfo != null) { + if (vehicleInfo != null) { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId} is using their own passenger car. CurrentPathMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId} is using their own passenger car. CurrentPathMode={extInstance.pathMode}"); #endif - // determine current position vector - Vector3 currentPos; - ushort currentBuildingId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].GetBuildingByLocation(); - if (currentBuildingId != 0) { - currentPos = Singleton.instance.m_buildings.m_buffer[currentBuildingId].m_position; + // determine current position vector + Vector3 currentPos; + ushort currentBuildingId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].GetBuildingByLocation(); + if (currentBuildingId != 0) { + currentPos = Singleton.instance.m_buildings.m_buffer[currentBuildingId].m_position; #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Taking current position from current building {currentBuildingId} for citizen {instanceData.m_citizen} (citizen instance {instanceId}): {currentPos} CurrentPathMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Taking current position from current building {currentBuildingId} for citizen {instanceData.m_citizen} (citizen instance {instanceId}): {currentPos} CurrentPathMode={extInstance.pathMode}"); #endif - } else { - currentBuildingId = sourceBuildingId; - currentPos = instanceData.GetLastFramePosition(); + } else { + currentBuildingId = sourceBuildingId; + currentPos = instanceData.GetLastFramePosition(); #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Taking current position from last frame position for citizen {instanceData.m_citizen} (citizen instance {instanceId}): {currentPos}. Home {homeId} pos: {Singleton.instance.m_buildings.m_buffer[homeId].m_position} CurrentPathMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Taking current position from last frame position for citizen {instanceData.m_citizen} (citizen instance {instanceId}): {currentPos}. Home {homeId} pos: {Singleton.instance.m_buildings.m_buffer[homeId].m_position} CurrentPathMode={extInstance.pathMode}"); #endif - } + } - // spawn a passenger car near the current position - Vector3 parkPos; - ParkingUnableReason parkReason; - if (AdvancedParkingManager.Instance.TrySpawnParkedPassengerCar(instanceData.m_citizen, homeId, currentPos, vehicleInfo, out parkPos, out parkReason)) { - parkedVehicleId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; + // spawn a passenger car near the current position + Vector3 parkPos; + ParkingUnableReason parkReason; + if (AdvancedParkingManager.Instance.TrySpawnParkedPassengerCar(instanceData.m_citizen, homeId, currentPos, vehicleInfo, out parkPos, out parkReason)) { + parkedVehicleId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Parked vehicle for citizen {instanceData.m_citizen} (instance {instanceId}) is {parkedVehicleId} now (parkPos={parkPos})."); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Parked vehicle for citizen {instanceData.m_citizen} (instance {instanceId}) is {parkedVehicleId} now (parkPos={parkPos})."); #endif - if (currentBuildingId != 0) { - extBuildingMan.ModifyParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], parkPos, GlobalConfig.Instance.ParkingAI.MinSpawnedCarParkingSpaceDemandDelta, GlobalConfig.Instance.ParkingAI.MaxSpawnedCarParkingSpaceDemandDelta); - } - } else { + if (currentBuildingId != 0) { + extBuildingMan.ModifyParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], parkPos, GlobalConfig.Instance.ParkingAI.MinSpawnedCarParkingSpaceDemandDelta, GlobalConfig.Instance.ParkingAI.MaxSpawnedCarParkingSpaceDemandDelta); + } + } else { #if DEBUG - if (debug) { - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): >> Failed to spawn parked vehicle for citizen {instanceData.m_citizen} (citizen instance {instanceId}). reason={parkReason}. homePos: {Singleton.instance.m_buildings.m_buffer[homeId].m_position}"); - } + if (debug) { + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): >> Failed to spawn parked vehicle for citizen {instanceData.m_citizen} (citizen instance {instanceId}). reason={parkReason}. homePos: {Singleton.instance.m_buildings.m_buffer[homeId].m_position}"); + } #endif - if (parkReason == ParkingUnableReason.NoSpaceFound && currentBuildingId != 0) { - extBuildingMan.AddParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], GlobalConfig.Instance.ParkingAI.FailedSpawnParkingSpaceDemandIncrement); - } - } - } else { + if (parkReason == ParkingUnableReason.NoSpaceFound && currentBuildingId != 0) { + extBuildingMan.AddParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], GlobalConfig.Instance.ParkingAI.FailedSpawnParkingSpaceDemandIncrement); + } + } + } else { #if DEBUG - if (debug) { - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId}, home {homeId} does not own a vehicle."); - } + if (debug) { + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId}, home {homeId} does not own a vehicle."); + } #endif - } - } + } + } - if (parkedVehicleId != 0) { - // citizen has to reach their parked vehicle first + if (parkedVehicleId != 0) { + // citizen has to reach their parked vehicle first #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Calculating path to reach parked vehicle {parkedVehicleId} for citizen instance {instanceId}. targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Calculating path to reach parked vehicle {parkedVehicleId} for citizen instance {instanceId}. targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); #endif - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; - return ExtSoftPathState.FailedSoft; - } else { - // error! could not find/spawn parked car + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; + return ExtSoftPathState.FailedSoft; + } else { + // error! could not find/spawn parked car #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} still does not have a parked vehicle! Retrying: Cim should walk to target"); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} still does not have a parked vehicle! Retrying: Cim should walk to target"); #endif - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; - return ExtSoftPathState.FailedSoft; - } - } else { - // path does not contain a car section: path can be reused for walking + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; + return ExtSoftPathState.FailedSoft; + } + } else { + // path does not contain a car section: path can be reused for walking #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A direct car path OR initial path was queried that does not contain a car section. Switching path mode to walking."); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A direct car path OR initial path was queried that does not contain a car section. Switching path mode to walking."); #endif - if (usesPublicTransport) { - // decrease public tranport demand - if (instanceData.m_sourceBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); - } - if (instanceData.m_targetBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); - } - extCitizen.transportMode |= ExtTransportMode.PublicTransport; - } + if (usesPublicTransport) { + // decrease public tranport demand + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); + } + if (instanceData.m_targetBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); + } + extCitizen.transportMode |= ExtTransportMode.PublicTransport; + } - extInstance.pathMode = ExtPathMode.WalkingToTarget; - return ExtSoftPathState.Ready; - } - case ExtPathMode.CalculatingCarPathToTarget: // citizen has not yet entered their car (but is close to do so) and tries to reach the target directly - case ExtPathMode.CalculatingCarPathToKnownParkPos: // citizen has not yet entered their (but is close to do so) car and tries to reach a parking space in the vicinity of the target - case ExtPathMode.CalculatingCarPathToAltParkPos: // citizen has not yet entered their car (but is close to do so) and tries to reach an alternative parking space in the vicinity of the target - if (usesCar) { - // parked car should be reached now + extInstance.pathMode = ExtPathMode.WalkingToTarget; + return ExtSoftPathState.Ready; + } + case ExtPathMode.CalculatingCarPathToTarget: // citizen has not yet entered their car (but is close to do so) and tries to reach the target directly + case ExtPathMode.CalculatingCarPathToKnownParkPos: // citizen has not yet entered their (but is close to do so) car and tries to reach a parking space in the vicinity of the target + case ExtPathMode.CalculatingCarPathToAltParkPos: // citizen has not yet entered their car (but is close to do so) and tries to reach an alternative parking space in the vicinity of the target + if (usesCar) { + // parked car should be reached now #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path for citizen instance {instanceId} contains passenger car section and citizen should stand in front of their car."); + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path for citizen instance {instanceId} contains passenger car section and citizen should stand in front of their car."); #endif - if (extInstance.atOutsideConnection) { - // car path calculated starting at road outside connection: success - if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { - extInstance.pathMode = ExtPathMode.DrivingToAltParkPos; - extInstance.parkingPathStartPosition = null; -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path to an alternative parking position is READY! CurrentPathMode={extInstance.pathMode}"); -#endif - } else if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget) { - extInstance.pathMode = ExtPathMode.DrivingToTarget; -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Car path is READY! CurrentPathMode={extInstance.pathMode}"); -#endif - } else if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos) { - extInstance.pathMode = ExtPathMode.DrivingToKnownParkPos; -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Car path to known parking position is READY! CurrentPathMode={extInstance.pathMode}"); -#endif - } - extInstance.atOutsideConnection = false; // citizen leaves outside connection - return ExtSoftPathState.Ready; - } else if (parkedVehicleId == 0) { - // error! could not find/spawn parked car -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} still does not have a parked vehicle! Retrying: Cim should walk to target"); -#endif - - extCitInstMan.Reset(ref extInstance); - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; - return ExtSoftPathState.FailedSoft; - } else if (sqrDistToParkedVehicle > 4f * GlobalConfig.Instance.ParkingAI.MaxParkedCarInstanceSwitchSqrDistance) { - // error! parked car is too far away -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} cannot enter parked vehicle because it is too far away (sqrDistToParkedVehicle={sqrDistToParkedVehicle})! Retrying: Cim should walk to parked car"); -#endif - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; - return ExtSoftPathState.FailedSoft; - } else { - // path using passenger car has been calculated - ushort vehicleId; - if (EnterParkedCar(instanceId, ref instanceData, parkedVehicleId, out vehicleId)) { - extInstance.pathMode = extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget ? ExtPathMode.DrivingToTarget : ExtPathMode.DrivingToKnownParkPos; - extCitizen.transportMode |= ExtTransportMode.Car; - -#if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} has entered their car and is now travelling by car (vehicleId={vehicleId}). CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); -#endif - return ExtSoftPathState.Ignore; - } else { - // error! parked car could not be entered (reached vehicle limit?): try to walk to target -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Entering parked vehicle {parkedVehicleId} failed for citizen instance {instanceId}. Trying to walk to target. CurrentDepartureMode={extInstance.pathMode}"); -#endif - - extCitInstMan.Reset(ref extInstance); - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; - return ExtSoftPathState.FailedSoft; - } - } - } else { - // citizen does not need a car for the calculated path... - switch (extInstance.pathMode) { - case ExtPathMode.CalculatingCarPathToTarget: - // ... and the path can be reused for walking -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A direct car path was queried that does not contain a car section. Switching path mode to walking."); -#endif - extCitInstMan.Reset(ref extInstance); - - if (usesPublicTransport) { - // decrease public tranport demand - if (instanceData.m_sourceBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); - } - if (instanceData.m_targetBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); - } - extCitizen.transportMode |= ExtTransportMode.PublicTransport; - } - - extInstance.pathMode = ExtPathMode.WalkingToTarget; - return ExtSoftPathState.Ready; - case ExtPathMode.CalculatingCarPathToKnownParkPos: - case ExtPathMode.CalculatingCarPathToAltParkPos: - default: - // ... and a path to a parking spot was calculated: dismiss path and restart path-finding for walking -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A parking space car path was queried but it turned out that no car is needed. Retrying path-finding for walking."); -#endif - extCitInstMan.Reset(ref extInstance); - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; - return ExtSoftPathState.FailedSoft; - } - } - case ExtPathMode.CalculatingWalkingPathToParkedCar: - // path to parked vehicle has been calculated... - if (parkedVehicleId == 0) { - // ... but the parked vehicle has vanished -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} shall walk to their parked vehicle but it disappeared. Retrying path-find for walking."); -#endif - extCitInstMan.Reset(ref extInstance); - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; - return ExtSoftPathState.FailedSoft; - } else { - extInstance.pathMode = ExtPathMode.WalkingToParkedCar; -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} is now on their way to its parked vehicle. CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); -#endif - return ExtSoftPathState.Ready; - } - case ExtPathMode.CalculatingWalkingPathToTarget: - // final walking path to target has been calculated - extInstance.pathMode = ExtPathMode.WalkingToTarget; -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} is now travelling by foot to their final target. CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); -#endif - return ExtSoftPathState.Ready; - } - } else { - // citizen has a vehicle assigned - -#if DEBUG - if (debug) - Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen has a vehicle assigned but this method does not handle this situation. Forcing path-find to fail."); -#endif - extCitInstMan.Reset(ref extInstance); - return ExtSoftPathState.FailedHard; - } - } - - /// - /// Handles a path-finding failure for citizen instances and activated Parking AI. - /// - /// Citizen instance id - /// Citizen instance data - /// extended citizen instance information - /// extended citizen information - /// if true path-finding may be repeated (path mode has been updated), false otherwise - protected ExtSoftPathState OnCitizenPathFindFailure(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; - -#if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path-finding failed for citizen instance {extInstance.instanceId}. CurrentPathMode={extInstance.pathMode}"); -#endif - - // update public transport demands - switch (extInstance.pathMode) { - case ExtPathMode.None: - case ExtPathMode.CalculatingWalkingPathToTarget: - case ExtPathMode.CalculatingWalkingPathToParkedCar: - case ExtPathMode.TaxiToTarget: - // could not reach target building by walking/driving/public transport: increase public transport demand - if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { -#if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Increasing public transport demand of target building {instanceData.m_targetBuilding} and source building {instanceData.m_sourceBuilding}"); -#endif - - if (instanceData.m_targetBuilding != 0) { - extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, false); - } - if (instanceData.m_sourceBuilding != 0) { - extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, true); - } - } - break; - } - - /* - * relocate parked car if abandoned - */ - if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar) { - /* - * parked car is unreachable - */ - ushort parkedVehicleId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; - if (parkedVehicleId != 0) { - /* - * parked car is present - */ - - ushort homeId = 0; - Services.CitizenService.ProcessCitizen(extCitizen.citizenId, delegate (uint citId, ref Citizen cit) { - homeId = cit.m_homeBuilding; - return true; - }); - - // calculate distance between citizen and parked car - bool movedCar = false; - Vector3 citizenPos = instanceData.GetLastFramePosition(); - float parkedToCitizen = 0f; - Vector3 oldParkedVehiclePos = default(Vector3); - Services.VehicleService.ProcessParkedVehicle(parkedVehicleId, delegate (ushort parkedVehId, ref VehicleParked parkedVehicle) { - oldParkedVehiclePos = parkedVehicle.m_position; - parkedToCitizen = (parkedVehicle.m_position - citizenPos).magnitude; - if (parkedToCitizen > GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome) { - /* - * parked car is far away from current location - * -> relocate parked car and try again - */ - movedCar = TryMoveParkedVehicle(parkedVehicleId, ref parkedVehicle, citizenPos, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome, homeId); - } - return true; - }); - - if (movedCar) { - /* - * successfully moved the parked car to a closer location - * -> retry path-finding - */ - - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; -#if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Relocated parked car {parkedVehicleId} to a closer location (old pos/distance: {oldParkedVehiclePos}/{parkedToCitizen}, new pos/distance: {Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position}/{(Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position - citizenPos).magnitude}) for citizen @ {citizenPos}. Retrying path-finding. CurrentPathMode={extInstance.pathMode}"); -#endif - - return ExtSoftPathState.FailedSoft; - } else { - /* - * could not move car - * -> despawn parked car, walk to target or use public transport - */ -#if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Releasing unreachable parked vehicle {parkedVehicleId} for citizen instance {extInstance.instanceId}. CurrentPathMode={extInstance.pathMode}"); -#endif - Singleton.instance.ReleaseParkedVehicle(parkedVehicleId); - } - } - } + if (extInstance.atOutsideConnection) { + // car path calculated starting at road outside connection: success + if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { + extInstance.pathMode = ExtPathMode.DrivingToAltParkPos; + extInstance.parkingPathStartPosition = null; +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path to an alternative parking position is READY! CurrentPathMode={extInstance.pathMode}"); +#endif + } else if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget) { + extInstance.pathMode = ExtPathMode.DrivingToTarget; +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Car path is READY! CurrentPathMode={extInstance.pathMode}"); +#endif + } else if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos) { + extInstance.pathMode = ExtPathMode.DrivingToKnownParkPos; +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Car path to known parking position is READY! CurrentPathMode={extInstance.pathMode}"); +#endif + } + extInstance.atOutsideConnection = false; // citizen leaves outside connection + return ExtSoftPathState.Ready; + } else if (parkedVehicleId == 0) { + // error! could not find/spawn parked car +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} still does not have a parked vehicle! Retrying: Cim should walk to target"); +#endif + + extCitInstMan.Reset(ref extInstance); + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; + return ExtSoftPathState.FailedSoft; + } else if (sqrDistToParkedVehicle > 4f * GlobalConfig.Instance.ParkingAI.MaxParkedCarInstanceSwitchSqrDistance) { + // error! parked car is too far away +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} cannot enter parked vehicle because it is too far away (sqrDistToParkedVehicle={sqrDistToParkedVehicle})! Retrying: Cim should walk to parked car"); +#endif + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; + return ExtSoftPathState.FailedSoft; + } else { + // path using passenger car has been calculated + ushort vehicleId; + if (EnterParkedCar(instanceId, ref instanceData, parkedVehicleId, out vehicleId)) { + extInstance.pathMode = extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget ? ExtPathMode.DrivingToTarget : ExtPathMode.DrivingToKnownParkPos; + extCitizen.transportMode |= ExtTransportMode.Car; + +#if DEBUG + if (fineDebug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} has entered their car and is now travelling by car (vehicleId={vehicleId}). CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); +#endif + return ExtSoftPathState.Ignore; + } else { + // error! parked car could not be entered (reached vehicle limit?): try to walk to target +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Entering parked vehicle {parkedVehicleId} failed for citizen instance {instanceId}. Trying to walk to target. CurrentDepartureMode={extInstance.pathMode}"); +#endif + + extCitInstMan.Reset(ref extInstance); + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; + return ExtSoftPathState.FailedSoft; + } + } + } else { + // citizen does not need a car for the calculated path... + switch (extInstance.pathMode) { + case ExtPathMode.CalculatingCarPathToTarget: + // ... and the path can be reused for walking +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A direct car path was queried that does not contain a car section. Switching path mode to walking."); +#endif + extCitInstMan.Reset(ref extInstance); + + if (usesPublicTransport) { + // decrease public tranport demand + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); + } + if (instanceData.m_targetBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); + } + extCitizen.transportMode |= ExtTransportMode.PublicTransport; + } + + extInstance.pathMode = ExtPathMode.WalkingToTarget; + return ExtSoftPathState.Ready; + case ExtPathMode.CalculatingCarPathToKnownParkPos: + case ExtPathMode.CalculatingCarPathToAltParkPos: + default: + // ... and a path to a parking spot was calculated: dismiss path and restart path-finding for walking +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A parking space car path was queried but it turned out that no car is needed. Retrying path-finding for walking."); +#endif + extCitInstMan.Reset(ref extInstance); + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; + return ExtSoftPathState.FailedSoft; + } + } + case ExtPathMode.CalculatingWalkingPathToParkedCar: + // path to parked vehicle has been calculated... + if (parkedVehicleId == 0) { + // ... but the parked vehicle has vanished +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} shall walk to their parked vehicle but it disappeared. Retrying path-find for walking."); +#endif + extCitInstMan.Reset(ref extInstance); + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; + return ExtSoftPathState.FailedSoft; + } else { + extInstance.pathMode = ExtPathMode.WalkingToParkedCar; +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} is now on their way to its parked vehicle. CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); +#endif + return ExtSoftPathState.Ready; + } + case ExtPathMode.CalculatingWalkingPathToTarget: + // final walking path to target has been calculated + extInstance.pathMode = ExtPathMode.WalkingToTarget; +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} is now travelling by foot to their final target. CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); +#endif + return ExtSoftPathState.Ready; + } + } else { + // citizen has a vehicle assigned + +#if DEBUG + if (debug) + Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen has a vehicle assigned but this method does not handle this situation. Forcing path-find to fail."); +#endif + extCitInstMan.Reset(ref extInstance); + return ExtSoftPathState.FailedHard; + } + } + + /// + /// Handles a path-finding failure for citizen instances and activated Parking AI. + /// + /// Citizen instance id + /// Citizen instance data + /// extended citizen instance information + /// extended citizen information + /// if true path-finding may be repeated (path mode has been updated), false otherwise + protected ExtSoftPathState OnCitizenPathFindFailure(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; + +#if DEBUG + bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path-finding failed for citizen instance {extInstance.instanceId}. CurrentPathMode={extInstance.pathMode}"); +#endif + + // update public transport demands + switch (extInstance.pathMode) { + case ExtPathMode.None: + case ExtPathMode.CalculatingWalkingPathToTarget: + case ExtPathMode.CalculatingWalkingPathToParkedCar: + case ExtPathMode.TaxiToTarget: + // could not reach target building by walking/driving/public transport: increase public transport demand + if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { +#if DEBUG + if (fineDebug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Increasing public transport demand of target building {instanceData.m_targetBuilding} and source building {instanceData.m_sourceBuilding}"); +#endif + + if (instanceData.m_targetBuilding != 0) { + extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, false); + } + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, true); + } + } + break; + } + + /* + * relocate parked car if abandoned + */ + if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar) { + /* + * parked car is unreachable + */ + ushort parkedVehicleId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; + if (parkedVehicleId != 0) { + /* + * parked car is present + */ + + ushort homeId = 0; + Services.CitizenService.ProcessCitizen(extCitizen.citizenId, delegate (uint citId, ref Citizen cit) { + homeId = cit.m_homeBuilding; + return true; + }); + + // calculate distance between citizen and parked car + bool movedCar = false; + Vector3 citizenPos = instanceData.GetLastFramePosition(); + float parkedToCitizen = 0f; + Vector3 oldParkedVehiclePos = default(Vector3); + Services.VehicleService.ProcessParkedVehicle(parkedVehicleId, delegate (ushort parkedVehId, ref VehicleParked parkedVehicle) { + oldParkedVehiclePos = parkedVehicle.m_position; + parkedToCitizen = (parkedVehicle.m_position - citizenPos).magnitude; + if (parkedToCitizen > GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome) { + /* + * parked car is far away from current location + * -> relocate parked car and try again + */ + movedCar = TryMoveParkedVehicle(parkedVehicleId, ref parkedVehicle, citizenPos, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome, homeId); + } + return true; + }); + + if (movedCar) { + /* + * successfully moved the parked car to a closer location + * -> retry path-finding + */ + + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; +#if DEBUG + if (fineDebug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Relocated parked car {parkedVehicleId} to a closer location (old pos/distance: {oldParkedVehiclePos}/{parkedToCitizen}, new pos/distance: {Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position}/{(Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position - citizenPos).magnitude}) for citizen @ {citizenPos}. Retrying path-finding. CurrentPathMode={extInstance.pathMode}"); +#endif + + return ExtSoftPathState.FailedSoft; + } else { + /* + * could not move car + * -> despawn parked car, walk to target or use public transport + */ +#if DEBUG + if (fineDebug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Releasing unreachable parked vehicle {parkedVehicleId} for citizen instance {extInstance.instanceId}. CurrentPathMode={extInstance.pathMode}"); +#endif + Singleton.instance.ReleaseParkedVehicle(parkedVehicleId); + } + } + } + + // check if path-finding may be repeated + ExtSoftPathState ret = ExtSoftPathState.FailedHard; + switch (extInstance.pathMode) { + case ExtPathMode.CalculatingCarPathToTarget: + case ExtPathMode.CalculatingCarPathToKnownParkPos: + case ExtPathMode.CalculatingWalkingPathToParkedCar: + // try to walk to target +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path failed but it may be retried to walk to the target."); +#endif + extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; + ret = ExtSoftPathState.FailedSoft; + break; + default: +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path failed and walking to target is not an option. Resetting ext. instance."); +#endif + extCitInstMan.Reset(ref extInstance); + break; + } + +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Setting CurrentPathMode for citizen instance {extInstance.instanceId} to {extInstance.pathMode}, ret={ret}"); +#endif + + // reset current transport mode for hard failures + if (ret == ExtSoftPathState.FailedHard) { + extCitizen.transportMode = ExtTransportMode.None; + } + + return ret; + } + + /// + /// Handles a path-finding failure for citizen instances and activated Parking AI. + /// + /// Vehicle id + /// Vehicle data + /// Driver citizen instance data + /// extended citizen instance information of driver + /// if true path-finding may be repeated (path mode has been updated), false otherwise + protected ExtSoftPathState OnCarPathFindFailure(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstanceData, ref ExtCitizenInstance driverExtInstance) { + IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; +#if DEBUG + bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId) && + (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstanceData.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstanceData.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstanceData.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (debug) + Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path-finding failed for driver citizen instance {driverExtInstance.instanceId}. CurrentPathMode={driverExtInstance.pathMode}"); +#endif + + // update parking demands + switch (driverExtInstance.pathMode) { + case ExtPathMode.None: + case ExtPathMode.CalculatingCarPathToAltParkPos: + case ExtPathMode.CalculatingCarPathToKnownParkPos: + // could not reach target building by driving: increase parking space demand +#if DEBUG + if (fineDebug) + Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Increasing parking space demand of target building {driverInstanceData.m_targetBuilding}"); +#endif + if (driverInstanceData.m_targetBuilding != 0) { + IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; + extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[driverInstanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.FailedParkingSpaceDemandIncrement); + } + break; + } + + // check if path-finding may be repeated + ExtSoftPathState ret = ExtSoftPathState.FailedHard; + switch (driverExtInstance.pathMode) { + case ExtPathMode.CalculatingCarPathToAltParkPos: + case ExtPathMode.CalculatingCarPathToKnownParkPos: + // try to drive directly to the target if public transport is allowed + if ((driverInstanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path failed but it may be retried to drive directly to the target / using public transport."); +#endif + driverExtInstance.pathMode = ExtPathMode.RequiresMixedCarPathToTarget; + ret = ExtSoftPathState.FailedSoft; + } + break; + default: +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path failed and a direct target is not an option. Resetting driver ext. instance."); +#endif + extCitizenInstanceManager.Reset(ref driverExtInstance); + break; + } + +#if DEBUG + if (debug) + Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Setting CurrentPathMode for driver citizen instance {driverExtInstance.instanceId} to {driverExtInstance.pathMode}, ret={ret}"); +#endif + + return ret; + } + + public bool TryMoveParkedVehicle(ushort parkedVehicleId, ref VehicleParked parkedVehicle, Vector3 refPos, float maxDistance, ushort homeId) { + ExtParkingSpaceLocation parkingSpaceLocation; + ushort parkingSpaceLocationId; + Vector3 parkPos; + Quaternion parkRot; + float parkOffset; - // check if path-finding may be repeated - ExtSoftPathState ret = ExtSoftPathState.FailedHard; - switch (extInstance.pathMode) { - case ExtPathMode.CalculatingCarPathToTarget: - case ExtPathMode.CalculatingCarPathToKnownParkPos: - case ExtPathMode.CalculatingWalkingPathToParkedCar: - // try to walk to target -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path failed but it may be retried to walk to the target."); -#endif - extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; - ret = ExtSoftPathState.FailedSoft; - break; - default: -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path failed and walking to target is not an option. Resetting ext. instance."); -#endif - extCitInstMan.Reset(ref extInstance); - break; - } - -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Setting CurrentPathMode for citizen instance {extInstance.instanceId} to {extInstance.pathMode}, ret={ret}"); -#endif - - // reset current transport mode for hard failures - if (ret == ExtSoftPathState.FailedHard) { - extCitizen.transportMode = ExtTransportMode.None; - } - - return ret; - } - - /// - /// Handles a path-finding failure for citizen instances and activated Parking AI. - /// - /// Vehicle id - /// Vehicle data - /// Driver citizen instance data - /// extended citizen instance information of driver - /// if true path-finding may be repeated (path mode has been updated), false otherwise - protected ExtSoftPathState OnCarPathFindFailure(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstanceData, ref ExtCitizenInstance driverExtInstance) { - IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; -#if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId) && - (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstanceData.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstanceData.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstanceData.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path-finding failed for driver citizen instance {driverExtInstance.instanceId}. CurrentPathMode={driverExtInstance.pathMode}"); -#endif - - // update parking demands - switch (driverExtInstance.pathMode) { - case ExtPathMode.None: - case ExtPathMode.CalculatingCarPathToAltParkPos: - case ExtPathMode.CalculatingCarPathToKnownParkPos: - // could not reach target building by driving: increase parking space demand -#if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Increasing parking space demand of target building {driverInstanceData.m_targetBuilding}"); -#endif - if (driverInstanceData.m_targetBuilding != 0) { - IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; - extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[driverInstanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.FailedParkingSpaceDemandIncrement); - } - break; - } - - // check if path-finding may be repeated - ExtSoftPathState ret = ExtSoftPathState.FailedHard; - switch (driverExtInstance.pathMode) { - case ExtPathMode.CalculatingCarPathToAltParkPos: - case ExtPathMode.CalculatingCarPathToKnownParkPos: - // try to drive directly to the target if public transport is allowed - if ((driverInstanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path failed but it may be retried to drive directly to the target / using public transport."); -#endif - driverExtInstance.pathMode = ExtPathMode.RequiresMixedCarPathToTarget; - ret = ExtSoftPathState.FailedSoft; - } - break; - default: -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path failed and a direct target is not an option. Resetting driver ext. instance."); -#endif - extCitizenInstanceManager.Reset(ref driverExtInstance); - break; - } - -#if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Setting CurrentPathMode for driver citizen instance {driverExtInstance.instanceId} to {driverExtInstance.pathMode}, ret={ret}"); -#endif - - return ret; - } - - public bool TryMoveParkedVehicle(ushort parkedVehicleId, ref VehicleParked parkedVehicle, Vector3 refPos, float maxDistance, ushort homeId) { - ExtParkingSpaceLocation parkingSpaceLocation; - ushort parkingSpaceLocationId; - Vector3 parkPos; - Quaternion parkRot; - float parkOffset; - - bool found = false; + bool found = false; #if BENCHMARK using (var bm = new Benchmark(null, "FindParkingSpaceInVicinity")) { #endif - found = AdvancedParkingManager.Instance.FindParkingSpaceInVicinity(refPos, Vector3.zero, parkedVehicle.Info, homeId, 0, maxDistance, out parkingSpaceLocation, out parkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); + found = AdvancedParkingManager.Instance.FindParkingSpaceInVicinity(refPos, Vector3.zero, parkedVehicle.Info, homeId, 0, maxDistance, out parkingSpaceLocation, out parkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); #if BENCHMARK } #endif - if (found) { - Singleton.instance.RemoveFromGrid(parkedVehicleId, ref parkedVehicle); - parkedVehicle.m_position = parkPos; - parkedVehicle.m_rotation = parkRot; - Singleton.instance.AddToGrid(parkedVehicleId, ref parkedVehicle); - } + if (found) { + Singleton.instance.RemoveFromGrid(parkedVehicleId, ref parkedVehicle); + parkedVehicle.m_position = parkPos; + parkedVehicle.m_rotation = parkRot; + Singleton.instance.AddToGrid(parkedVehicleId, ref parkedVehicle); + } - return found; - } + return found; + } - public bool FindParkingSpaceForCitizen(Vector3 endPos, VehicleInfo vehicleInfo, ref ExtCitizenInstance extDriverInstance, ushort homeId, bool goingHome, ushort vehicleId, bool allowTourists, out Vector3 parkPos, ref PathUnit.Position endPathPos, out bool calculateEndPos) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + public bool FindParkingSpaceForCitizen(Vector3 endPos, VehicleInfo vehicleInfo, ref ExtCitizenInstance extDriverInstance, ushort homeId, bool goingHome, ushort vehicleId, bool allowTourists, out Vector3 parkPos, ref PathUnit.Position endPathPos, out bool calculateEndPos) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; #if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId) && - (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == extDriverInstance.instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == extCitInstMan.GetCitizenId(extDriverInstance.instanceId)) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[extDriverInstance.instanceId].m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[extDriverInstance.instanceId].m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId) && + (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == extDriverInstance.instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == extCitInstMan.GetCitizenId(extDriverInstance.instanceId)) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[extDriverInstance.instanceId].m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[extDriverInstance.instanceId].m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif - calculateEndPos = true; - parkPos = default(Vector3); + calculateEndPos = true; + parkPos = default(Vector3); - if (!allowTourists) { - // TODO remove this from this method - uint citizenId = extCitInstMan.GetCitizenId(extDriverInstance.instanceId); - if (citizenId == 0 || - (Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Tourist) != Citizen.Flags.None) - return false; - } + if (!allowTourists) { + // TODO remove this from this method + uint citizenId = extCitInstMan.GetCitizenId(extDriverInstance.instanceId); + if (citizenId == 0 || + (Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Tourist) != Citizen.Flags.None) + return false; + } #if DEBUG - if (fineDebug) - Log._Debug($"Citizen instance {extDriverInstance.instanceId} (CurrentPathMode={extDriverInstance.pathMode}) can still use their passenger car and is either not a tourist or wants to find an alternative parking spot. Finding a parking space before starting path-finding."); + if (fineDebug) + Log._Debug($"Citizen instance {extDriverInstance.instanceId} (CurrentPathMode={extDriverInstance.pathMode}) can still use their passenger car and is either not a tourist or wants to find an alternative parking spot. Finding a parking space before starting path-finding."); #endif - ExtParkingSpaceLocation knownParkingSpaceLocation; - ushort knownParkingSpaceLocationId; - Quaternion parkRot; - float parkOffset; + ExtParkingSpaceLocation knownParkingSpaceLocation; + ushort knownParkingSpaceLocationId; + Quaternion parkRot; + float parkOffset; - // find a free parking space - bool success = FindParkingSpaceInVicinity(endPos, Vector3.zero, vehicleInfo, homeId, vehicleId, goingHome ? GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome : GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out knownParkingSpaceLocation, out knownParkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); + // find a free parking space + bool success = FindParkingSpaceInVicinity(endPos, Vector3.zero, vehicleInfo, homeId, vehicleId, goingHome ? GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome : GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out knownParkingSpaceLocation, out knownParkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); - extDriverInstance.parkingSpaceLocation = knownParkingSpaceLocation; - extDriverInstance.parkingSpaceLocationId = knownParkingSpaceLocationId; + extDriverInstance.parkingSpaceLocation = knownParkingSpaceLocation; + extDriverInstance.parkingSpaceLocationId = knownParkingSpaceLocationId; - if (success) { + if (success) { #if DEBUG - if (fineDebug) - Log._Debug($"Found a parking spot for citizen instance {extDriverInstance.instanceId} (CurrentPathMode={extDriverInstance.pathMode}) before starting car path: {knownParkingSpaceLocation} @ {knownParkingSpaceLocationId}"); + if (fineDebug) + Log._Debug($"Found a parking spot for citizen instance {extDriverInstance.instanceId} (CurrentPathMode={extDriverInstance.pathMode}) before starting car path: {knownParkingSpaceLocation} @ {knownParkingSpaceLocationId}"); #endif - if (knownParkingSpaceLocation == ExtParkingSpaceLocation.RoadSide) { - // found segment with parking space - Vector3 pedPos; - uint laneId; - int laneIndex; - float laneOffset; + if (knownParkingSpaceLocation == ExtParkingSpaceLocation.RoadSide) { + // found segment with parking space + Vector3 pedPos; + uint laneId; + int laneIndex; + float laneOffset; #if DEBUG - if (debug) - Log._Debug($"Found segment {knownParkingSpaceLocationId} for road-side parking position for citizen instance {extDriverInstance.instanceId}!"); + if (debug) + Log._Debug($"Found segment {knownParkingSpaceLocationId} for road-side parking position for citizen instance {extDriverInstance.instanceId}!"); #endif - // determine nearest sidewalk position for parking position at segment - if (Singleton.instance.m_segments.m_buffer[knownParkingSpaceLocationId].GetClosestLanePosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out pedPos, out laneId, out laneIndex, out laneOffset)) { - endPathPos.m_segment = knownParkingSpaceLocationId; - endPathPos.m_lane = (byte)laneIndex; - endPathPos.m_offset = (byte)(parkOffset * 255f); - calculateEndPos = false; + // determine nearest sidewalk position for parking position at segment + if (Singleton.instance.m_segments.m_buffer[knownParkingSpaceLocationId].GetClosestLanePosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out pedPos, out laneId, out laneIndex, out laneOffset)) { + endPathPos.m_segment = knownParkingSpaceLocationId; + endPathPos.m_lane = (byte)laneIndex; + endPathPos.m_offset = (byte)(parkOffset * 255f); + calculateEndPos = false; - //extDriverInstance.CurrentPathMode = successMode;// ExtCitizenInstance.PathMode.CalculatingKnownCarPath; + //extDriverInstance.CurrentPathMode = successMode;// ExtCitizenInstance.PathMode.CalculatingKnownCarPath; #if DEBUG - if (debug) - Log._Debug($"Found an parking spot sidewalk position for citizen instance {extDriverInstance.instanceId} @ segment {knownParkingSpaceLocationId}, laneId {laneId}, laneIndex {laneIndex}, offset={endPathPos.m_offset}! CurrentPathMode={extDriverInstance.pathMode}"); + if (debug) + Log._Debug($"Found an parking spot sidewalk position for citizen instance {extDriverInstance.instanceId} @ segment {knownParkingSpaceLocationId}, laneId {laneId}, laneIndex {laneIndex}, offset={endPathPos.m_offset}! CurrentPathMode={extDriverInstance.pathMode}"); #endif - return true; - } else { + return true; + } else { #if DEBUG - if (debug) - Log._Debug($"Could not find an alternative parking spot sidewalk position for citizen instance {extDriverInstance.instanceId}! CurrentPathMode={extDriverInstance.pathMode}"); + if (debug) + Log._Debug($"Could not find an alternative parking spot sidewalk position for citizen instance {extDriverInstance.instanceId}! CurrentPathMode={extDriverInstance.pathMode}"); #endif - return false; - } - } else if (knownParkingSpaceLocation == ExtParkingSpaceLocation.Building) { - // found a building with parking space - if (Constants.ManagerFactory.ExtPathManager.FindPathPositionWithSpiralLoop(parkPos, endPos, ItemClass.Service.Road, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out endPathPos)) { - calculateEndPos = false; - } + return false; + } + } else if (knownParkingSpaceLocation == ExtParkingSpaceLocation.Building) { + // found a building with parking space + if (Constants.ManagerFactory.ExtPathManager.FindPathPositionWithSpiralLoop(parkPos, endPos, ItemClass.Service.Road, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out endPathPos)) { + calculateEndPos = false; + } - //endPos = parkPos; + //endPos = parkPos; - //extDriverInstance.CurrentPathMode = successMode;// ExtCitizenInstance.PathMode.CalculatingKnownCarPath; + //extDriverInstance.CurrentPathMode = successMode;// ExtCitizenInstance.PathMode.CalculatingKnownCarPath; #if DEBUG - if (debug) - Log._Debug($"Navigating citizen instance {extDriverInstance.instanceId} to parking building {knownParkingSpaceLocationId}! segment={endPathPos.m_segment}, laneIndex={endPathPos.m_lane}, offset={endPathPos.m_offset}. CurrentPathMode={extDriverInstance.pathMode} calculateEndPos={calculateEndPos}"); + if (debug) + Log._Debug($"Navigating citizen instance {extDriverInstance.instanceId} to parking building {knownParkingSpaceLocationId}! segment={endPathPos.m_segment}, laneIndex={endPathPos.m_lane}, offset={endPathPos.m_offset}. CurrentPathMode={extDriverInstance.pathMode} calculateEndPos={calculateEndPos}"); #endif - return true; - } - } - return false; - } - - public bool TrySpawnParkedPassengerCar(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { -#if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (fineDebug && homeId != 0) - Log._Debug($"Trying to spawn parked passenger car for citizen {citizenId}, home {homeId} @ {refPos}"); -#endif - - Vector3 roadParkPos; - ParkingUnableReason roadParkReason; - bool roadParkSuccess = TrySpawnParkedPassengerCarRoadSide(citizenId, refPos, vehicleInfo, out roadParkPos, out roadParkReason); - - Vector3 buildingParkPos; - ParkingUnableReason buildingParkReason; - bool buildingParkSuccess = TrySpawnParkedPassengerCarBuilding(citizenId, homeId, refPos, vehicleInfo, out buildingParkPos, out buildingParkReason); - - if ((!roadParkSuccess && !buildingParkSuccess) || (roadParkSuccess && !buildingParkSuccess)) { - parkPos = roadParkPos; - reason = roadParkReason; - return roadParkSuccess; - } else if (buildingParkSuccess && !roadParkSuccess) { - parkPos = buildingParkPos; - reason = buildingParkReason; - return buildingParkSuccess; - } else if ((roadParkPos - refPos).sqrMagnitude < (buildingParkPos - refPos).sqrMagnitude) { - parkPos = roadParkPos; - reason = roadParkReason; - return roadParkSuccess; - } else { - parkPos = buildingParkPos; - reason = buildingParkReason; - return buildingParkSuccess; - } - } - - public bool TrySpawnParkedPassengerCarRoadSide(uint citizenId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { -#if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log._Debug($"Trying to spawn parked passenger car at road side for citizen {citizenId} @ {refPos}"); -#endif - parkPos = Vector3.zero; - Quaternion parkRot = Quaternion.identity; - float parkOffset = 0f; - - if (FindParkingSpaceRoadSide(0, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out parkPos, out parkRot, out parkOffset)) { - // position found, spawn a parked vehicle - ushort parkedVehicleId; - if (Singleton.instance.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, citizenId)) { - Singleton.instance.m_citizens.m_buffer[citizenId].SetParkedVehicle(citizenId, parkedVehicleId); - Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_flags &= (ushort)(VehicleParked.Flags.All & ~VehicleParked.Flags.Parking); -#if DEBUG - if (debug) - Log._Debug($"[SUCCESS] Spawned parked passenger car at road side for citizen {citizenId}: {parkedVehicleId} @ {parkPos}"); -#endif - reason = ParkingUnableReason.None; - return true; - } else { - reason = ParkingUnableReason.LimitHit; - } - } else { - reason = ParkingUnableReason.NoSpaceFound; - } + return true; + } + } + return false; + } + + public bool TrySpawnParkedPassengerCar(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { #if DEBUG - if (debug) - Log._Debug($"[FAIL] Failed to spawn parked passenger car at road side for citizen {citizenId}"); + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (fineDebug && homeId != 0) + Log._Debug($"Trying to spawn parked passenger car for citizen {citizenId}, home {homeId} @ {refPos}"); #endif - return false; - } - public bool TrySpawnParkedPassengerCarBuilding(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { + Vector3 roadParkPos; + ParkingUnableReason roadParkReason; + bool roadParkSuccess = TrySpawnParkedPassengerCarRoadSide(citizenId, refPos, vehicleInfo, out roadParkPos, out roadParkReason); + + Vector3 buildingParkPos; + ParkingUnableReason buildingParkReason; + bool buildingParkSuccess = TrySpawnParkedPassengerCarBuilding(citizenId, homeId, refPos, vehicleInfo, out buildingParkPos, out buildingParkReason); + + if ((!roadParkSuccess && !buildingParkSuccess) || (roadParkSuccess && !buildingParkSuccess)) { + parkPos = roadParkPos; + reason = roadParkReason; + return roadParkSuccess; + } else if (buildingParkSuccess && !roadParkSuccess) { + parkPos = buildingParkPos; + reason = buildingParkReason; + return buildingParkSuccess; + } else if ((roadParkPos - refPos).sqrMagnitude < (buildingParkPos - refPos).sqrMagnitude) { + parkPos = roadParkPos; + reason = roadParkReason; + return roadParkSuccess; + } else { + parkPos = buildingParkPos; + reason = buildingParkReason; + return buildingParkSuccess; + } + } + + public bool TrySpawnParkedPassengerCarRoadSide(uint citizenId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { #if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - if (fineDebug && homeId != 0) - Log._Debug($"Trying to spawn parked passenger car next to building for citizen {citizenId} @ {refPos}"); + if (debug) + Log._Debug($"Trying to spawn parked passenger car at road side for citizen {citizenId} @ {refPos}"); #endif - parkPos = Vector3.zero; - Quaternion parkRot = Quaternion.identity; - float parkOffset; + parkPos = Vector3.zero; + Quaternion parkRot = Quaternion.identity; + float parkOffset = 0f; + + if (FindParkingSpaceRoadSide(0, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out parkPos, out parkRot, out parkOffset)) { + // position found, spawn a parked vehicle + ushort parkedVehicleId; + if (Singleton.instance.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, citizenId)) { + Singleton.instance.m_citizens.m_buffer[citizenId].SetParkedVehicle(citizenId, parkedVehicleId); + Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_flags &= (ushort)(VehicleParked.Flags.All & ~VehicleParked.Flags.Parking); +#if DEBUG + if (debug) + Log._Debug($"[SUCCESS] Spawned parked passenger car at road side for citizen {citizenId}: {parkedVehicleId} @ {parkPos}"); +#endif + reason = ParkingUnableReason.None; + return true; + } else { + reason = ParkingUnableReason.LimitHit; + } + } else { + reason = ParkingUnableReason.NoSpaceFound; + } +#if DEBUG + if (debug) + Log._Debug($"[FAIL] Failed to spawn parked passenger car at road side for citizen {citizenId}"); +#endif + return false; + } + + public bool TrySpawnParkedPassengerCarBuilding(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { +#if DEBUG + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (fineDebug && homeId != 0) + Log._Debug($"Trying to spawn parked passenger car next to building for citizen {citizenId} @ {refPos}"); +#endif + parkPos = Vector3.zero; + Quaternion parkRot = Quaternion.identity; + float parkOffset; + + if (FindParkingSpaceBuilding(vehicleInfo, homeId, 0, 0, refPos, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out parkPos, out parkRot, out parkOffset)) { + // position found, spawn a parked vehicle + ushort parkedVehicleId; + if (Singleton.instance.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, citizenId)) { + Singleton.instance.m_citizens.m_buffer[citizenId].SetParkedVehicle(citizenId, parkedVehicleId); + Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_flags &= (ushort)(VehicleParked.Flags.All & ~VehicleParked.Flags.Parking); +#if DEBUG + if (fineDebug && homeId != 0) + Log._Debug($"[SUCCESS] Spawned parked passenger car next to building for citizen {citizenId}: {parkedVehicleId} @ {parkPos}"); +#endif + reason = ParkingUnableReason.None; + return true; + } else { + reason = ParkingUnableReason.LimitHit; + } + } else { + reason = ParkingUnableReason.NoSpaceFound; + } +#if DEBUG + if (debug && homeId != 0) + Log._Debug($"[FAIL] Failed to spawn parked passenger car next to building for citizen {citizenId}"); +#endif + return false; + } + + public bool FindParkingSpaceInVicinity(Vector3 targetPos, Vector3 searchDir, VehicleInfo vehicleInfo, ushort homeId, ushort vehicleId, float maxDist, out ExtParkingSpaceLocation parkingSpaceLocation, out ushort parkingSpaceLocationId, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { +#if DEBUG + bool vehDebug = GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId; + bool debug = GlobalConfig.Instance.Debug.Switches[22] && vehDebug; +#endif + + // TODO check isElectric + Vector3 roadParkPos; + Quaternion roadParkRot; + float roadParkOffset; + Vector3 buildingParkPos; + Quaternion buildingParkRot; + float buildingParkOffset; + + Vector3 refPos = targetPos + searchDir * 16f; + + // TODO depending on simulation accuracy, disable searching for both road-side and building parking spaces + ushort parkingSpaceSegmentId = FindParkingSpaceAtRoadSide(0, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, maxDist, true, out roadParkPos, out roadParkRot, out roadParkOffset); + ushort parkingBuildingId = FindParkingSpaceBuilding(vehicleInfo, homeId, 0, 0, refPos, maxDist, maxDist, true, out buildingParkPos, out buildingParkRot, out buildingParkOffset); + + if (parkingSpaceSegmentId != 0) { + if (parkingBuildingId != 0) { + Randomizer rng = Services.SimulationService.Randomizer; + + // choose nearest parking position, after a bit of randomization + if ((roadParkPos - targetPos).magnitude < (buildingParkPos - targetPos).magnitude + && rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) { + // road parking space is closer +#if DEBUG + if (debug) + Log._Debug($"Found an (alternative) road-side parking position for vehicle {vehicleId} @ segment {parkingSpaceSegmentId} after comparing distance with a bulding parking position @ {parkingBuildingId}!"); +#endif + parkPos = roadParkPos; + parkRot = roadParkRot; + parkOffset = roadParkOffset; + parkingSpaceLocation = ExtParkingSpaceLocation.RoadSide; + parkingSpaceLocationId = parkingSpaceSegmentId; + return true; + } else { + // choose building parking space +#if DEBUG + if (debug) + Log._Debug($"Found an alternative building parking position for vehicle {vehicleId} at building {parkingBuildingId} after comparing distance with a road-side parking position @ {parkingSpaceSegmentId}!"); +#endif + parkPos = buildingParkPos; + parkRot = buildingParkRot; + parkOffset = buildingParkOffset; + parkingSpaceLocation = ExtParkingSpaceLocation.Building; + parkingSpaceLocationId = parkingBuildingId; + return true; + } + } else { + // road-side but no building parking space found +#if DEBUG + if (debug) + Log._Debug($"Found an alternative road-side parking position for vehicle {vehicleId} @ segment {parkingSpaceSegmentId}!"); +#endif + parkPos = roadParkPos; + parkRot = roadParkRot; + parkOffset = roadParkOffset; + parkingSpaceLocation = ExtParkingSpaceLocation.RoadSide; + parkingSpaceLocationId = parkingSpaceSegmentId; + return true; + } + } else if (parkingBuildingId != 0) { + // building but no road-side parking space found +#if DEBUG + if (debug) + Log._Debug($"Found an alternative building parking position for vehicle {vehicleId} at building {parkingBuildingId}!"); +#endif + parkPos = buildingParkPos; + parkRot = buildingParkRot; + parkOffset = buildingParkOffset; + parkingSpaceLocation = ExtParkingSpaceLocation.Building; + parkingSpaceLocationId = parkingBuildingId; + return true; + } else { + //driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.AltParkFailed; + parkingSpaceLocation = ExtParkingSpaceLocation.None; + parkingSpaceLocationId = 0; + parkPos = default(Vector3); + parkRot = default(Quaternion); + parkOffset = -1f; +#if DEBUG + if (debug) + Log._Debug($"Could not find a road-side or building parking position for vehicle {vehicleId}!"); +#endif + return false; + } + } + + protected ushort FindParkingSpaceAtRoadSide(ushort ignoreParked, Vector3 refPos, float width, float length, float maxDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { +#if DEBUG + bool debug = GlobalConfig.Instance.Debug.Switches[22]; +#endif + + parkPos = Vector3.zero; + parkRot = Quaternion.identity; + parkOffset = 0f; + + int centerI = (int)(refPos.z / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); + int centerJ = (int)(refPos.x / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); + int radius = Math.Max(1, (int)(maxDistance / ((float)BuildingManager.BUILDINGGRID_CELL_SIZE / 2f)) + 1); + + NetManager netManager = Singleton.instance; + Randomizer rng = Singleton.instance.m_randomizer; + + ushort foundSegmentId = 0; + Vector3 myParkPos = parkPos; + Quaternion myParkRot = parkRot; + float myParkOffset = parkOffset; + + LoopUtil.SpiralLoop(centerI, centerJ, radius, radius, delegate (int i, int j) { + if (i < 0 || i >= BuildingManager.BUILDINGGRID_RESOLUTION || j < 0 || j >= BuildingManager.BUILDINGGRID_RESOLUTION) + return true; + + ushort segmentId = netManager.m_segmentGrid[i * BuildingManager.BUILDINGGRID_RESOLUTION + j]; + int iterations = 0; + while (segmentId != 0) { + uint laneId; + int laneIndex; + float laneOffset; + Vector3 innerParkPos; + Quaternion innerParkRot; + float innerParkOffset; + + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + Vector3 segCenter = netManager.m_segments.m_buffer[segmentId].m_bounds.center; + + // randomize target position to allow for opposite road-side parking + segCenter.x += Singleton.instance.m_randomizer.Int32(GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand) - GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand / 2u; + segCenter.z += Singleton.instance.m_randomizer.Int32(GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand) - GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand / 2u; + + if (netManager.m_segments.m_buffer[segmentId].GetClosestLanePosition(segCenter, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out innerParkPos, out laneId, out laneIndex, out laneOffset)) { + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + if (!Options.parkingRestrictionsEnabled || ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, laneInfo.m_finalDirection)) { + if (!Options.vehicleRestrictionsEnabled || (VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)laneIndex, laneInfo, VehicleRestrictionsMode.Configured) & ExtVehicleType.PassengerCar) != ExtVehicleType.None) { + + if (CustomPassengerCarAI.FindParkingSpaceRoadSide(ignoreParked, segmentId, innerParkPos, width, length, out innerParkPos, out innerParkRot, out innerParkOffset)) { +#if DEBUG + if (debug) + Log._Debug($"FindParkingSpaceRoadSide: Found a parking space for refPos {refPos}, segment center {segCenter} @ {innerParkPos}, laneId {laneId}, laneIndex {laneIndex}!"); +#endif + foundSegmentId = segmentId; + myParkPos = innerParkPos; + myParkRot = innerParkRot; + myParkOffset = innerParkOffset; + if (!randomize || rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) + return false; + } + } + } + } else { + /*if (debug) + Log._Debug($"FindParkingSpaceRoadSide: Could not find closest lane position for parking @ {segmentId}!");*/ + } + + segmentId = netManager.m_segments.m_buffer[segmentId].m_nextGridSegment; + if (++iterations >= NetManager.MAX_SEGMENT_COUNT) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } - if (FindParkingSpaceBuilding(vehicleInfo, homeId, 0, 0, refPos, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out parkPos, out parkRot, out parkOffset)) { - // position found, spawn a parked vehicle - ushort parkedVehicleId; - if (Singleton.instance.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, citizenId)) { - Singleton.instance.m_citizens.m_buffer[citizenId].SetParkedVehicle(citizenId, parkedVehicleId); - Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_flags &= (ushort)(VehicleParked.Flags.All & ~VehicleParked.Flags.Parking); + return true; + }); + + if (foundSegmentId == 0) { #if DEBUG - if (fineDebug && homeId != 0) - Log._Debug($"[SUCCESS] Spawned parked passenger car next to building for citizen {citizenId}: {parkedVehicleId} @ {parkPos}"); + if (debug) + Log._Debug($"FindParkingSpaceRoadSide: Could not find a parking space for refPos {refPos}!"); #endif - reason = ParkingUnableReason.None; - return true; - } else { - reason = ParkingUnableReason.LimitHit; - } - } else { - reason = ParkingUnableReason.NoSpaceFound; - } -#if DEBUG - if (debug && homeId != 0) - Log._Debug($"[FAIL] Failed to spawn parked passenger car next to building for citizen {citizenId}"); -#endif - return false; - } - - public bool FindParkingSpaceInVicinity(Vector3 targetPos, Vector3 searchDir, VehicleInfo vehicleInfo, ushort homeId, ushort vehicleId, float maxDist, out ExtParkingSpaceLocation parkingSpaceLocation, out ushort parkingSpaceLocationId, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { -#if DEBUG - bool vehDebug = GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId; - bool debug = GlobalConfig.Instance.Debug.Switches[22] && vehDebug; -#endif - - // TODO check isElectric - Vector3 roadParkPos; - Quaternion roadParkRot; - float roadParkOffset; - Vector3 buildingParkPos; - Quaternion buildingParkRot; - float buildingParkOffset; - - Vector3 refPos = targetPos + searchDir * 16f; - - // TODO depending on simulation accuracy, disable searching for both road-side and building parking spaces - ushort parkingSpaceSegmentId = FindParkingSpaceAtRoadSide(0, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, maxDist, true, out roadParkPos, out roadParkRot, out roadParkOffset); - ushort parkingBuildingId = FindParkingSpaceBuilding(vehicleInfo, homeId, 0, 0, refPos, maxDist, maxDist, true, out buildingParkPos, out buildingParkRot, out buildingParkOffset); - - if (parkingSpaceSegmentId != 0) { - if (parkingBuildingId != 0) { - Randomizer rng = Services.SimulationService.Randomizer; - - // choose nearest parking position, after a bit of randomization - if ((roadParkPos - targetPos).magnitude < (buildingParkPos - targetPos).magnitude - && rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) { - // road parking space is closer -#if DEBUG - if (debug) - Log._Debug($"Found an (alternative) road-side parking position for vehicle {vehicleId} @ segment {parkingSpaceSegmentId} after comparing distance with a bulding parking position @ {parkingBuildingId}!"); -#endif - parkPos = roadParkPos; - parkRot = roadParkRot; - parkOffset = roadParkOffset; - parkingSpaceLocation = ExtParkingSpaceLocation.RoadSide; - parkingSpaceLocationId = parkingSpaceSegmentId; - return true; - } else { - // choose building parking space -#if DEBUG - if (debug) - Log._Debug($"Found an alternative building parking position for vehicle {vehicleId} at building {parkingBuildingId} after comparing distance with a road-side parking position @ {parkingSpaceSegmentId}!"); -#endif - parkPos = buildingParkPos; - parkRot = buildingParkRot; - parkOffset = buildingParkOffset; - parkingSpaceLocation = ExtParkingSpaceLocation.Building; - parkingSpaceLocationId = parkingBuildingId; - return true; - } - } else { - // road-side but no building parking space found -#if DEBUG - if (debug) - Log._Debug($"Found an alternative road-side parking position for vehicle {vehicleId} @ segment {parkingSpaceSegmentId}!"); -#endif - parkPos = roadParkPos; - parkRot = roadParkRot; - parkOffset = roadParkOffset; - parkingSpaceLocation = ExtParkingSpaceLocation.RoadSide; - parkingSpaceLocationId = parkingSpaceSegmentId; - return true; - } - } else if (parkingBuildingId != 0) { - // building but no road-side parking space found -#if DEBUG - if (debug) - Log._Debug($"Found an alternative building parking position for vehicle {vehicleId} at building {parkingBuildingId}!"); -#endif - parkPos = buildingParkPos; - parkRot = buildingParkRot; - parkOffset = buildingParkOffset; - parkingSpaceLocation = ExtParkingSpaceLocation.Building; - parkingSpaceLocationId = parkingBuildingId; - return true; - } else { - //driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.AltParkFailed; - parkingSpaceLocation = ExtParkingSpaceLocation.None; - parkingSpaceLocationId = 0; - parkPos = default(Vector3); - parkRot = default(Quaternion); - parkOffset = -1f; -#if DEBUG - if (debug) - Log._Debug($"Could not find a road-side or building parking position for vehicle {vehicleId}!"); -#endif - return false; - } - } - - protected ushort FindParkingSpaceAtRoadSide(ushort ignoreParked, Vector3 refPos, float width, float length, float maxDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { -#if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[22]; -#endif - - parkPos = Vector3.zero; - parkRot = Quaternion.identity; - parkOffset = 0f; - - int centerI = (int)(refPos.z / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); - int centerJ = (int)(refPos.x / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); - int radius = Math.Max(1, (int)(maxDistance / ((float)BuildingManager.BUILDINGGRID_CELL_SIZE / 2f)) + 1); - - NetManager netManager = Singleton.instance; - Randomizer rng = Singleton.instance.m_randomizer; - - ushort foundSegmentId = 0; - Vector3 myParkPos = parkPos; - Quaternion myParkRot = parkRot; - float myParkOffset = parkOffset; - - LoopUtil.SpiralLoop(centerI, centerJ, radius, radius, delegate (int i, int j) { - if (i < 0 || i >= BuildingManager.BUILDINGGRID_RESOLUTION || j < 0 || j >= BuildingManager.BUILDINGGRID_RESOLUTION) - return true; - - ushort segmentId = netManager.m_segmentGrid[i * BuildingManager.BUILDINGGRID_RESOLUTION + j]; - int iterations = 0; - while (segmentId != 0) { - uint laneId; - int laneIndex; - float laneOffset; - Vector3 innerParkPos; - Quaternion innerParkRot; - float innerParkOffset; - - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - Vector3 segCenter = netManager.m_segments.m_buffer[segmentId].m_bounds.center; - - // randomize target position to allow for opposite road-side parking - segCenter.x += Singleton.instance.m_randomizer.Int32(GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand) - GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand / 2u; - segCenter.z += Singleton.instance.m_randomizer.Int32(GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand) - GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand / 2u; - - if (netManager.m_segments.m_buffer[segmentId].GetClosestLanePosition(segCenter, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out innerParkPos, out laneId, out laneIndex, out laneOffset)) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - if (!Options.parkingRestrictionsEnabled || ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, laneInfo.m_finalDirection)) { - if (!Options.vehicleRestrictionsEnabled || (VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)laneIndex, laneInfo, VehicleRestrictionsMode.Configured) & ExtVehicleType.PassengerCar) != ExtVehicleType.None) { - - if (CustomPassengerCarAI.FindParkingSpaceRoadSide(ignoreParked, segmentId, innerParkPos, width, length, out innerParkPos, out innerParkRot, out innerParkOffset)) { - #if DEBUG - if (debug) - Log._Debug($"FindParkingSpaceRoadSide: Found a parking space for refPos {refPos}, segment center {segCenter} @ {innerParkPos}, laneId {laneId}, laneIndex {laneIndex}!"); - #endif - foundSegmentId = segmentId; - myParkPos = innerParkPos; - myParkRot = innerParkRot; - myParkOffset = innerParkOffset; - if (!randomize || rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) - return false; - } - } - } - } else { - /*if (debug) - Log._Debug($"FindParkingSpaceRoadSide: Could not find closest lane position for parking @ {segmentId}!");*/ - } - - segmentId = netManager.m_segments.m_buffer[segmentId].m_nextGridSegment; - if (++iterations >= NetManager.MAX_SEGMENT_COUNT) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - - return true; - }); - - if (foundSegmentId == 0) { -#if DEBUG - if (debug) - Log._Debug($"FindParkingSpaceRoadSide: Could not find a parking space for refPos {refPos}!"); -#endif - return 0; - } + return 0; + } - parkPos = myParkPos; - parkRot = myParkRot; - parkOffset = myParkOffset; + parkPos = myParkPos; + parkRot = myParkRot; + parkOffset = myParkOffset; - return foundSegmentId; - } + return foundSegmentId; + } - protected ushort FindParkingSpaceBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort segmentId, Vector3 refPos, float maxBuildingDistance, float maxParkingSpaceDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { + protected ushort FindParkingSpaceBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort segmentId, Vector3 refPos, float maxBuildingDistance, float maxParkingSpaceDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[22]; + bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif - parkPos = Vector3.zero; - parkRot = Quaternion.identity; - parkOffset = -1f; + parkPos = Vector3.zero; + parkRot = Quaternion.identity; + parkOffset = -1f; - int centerI = (int)(refPos.z / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); - int centerJ = (int)(refPos.x / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); - int radius = Math.Max(1, (int)(maxBuildingDistance / ((float)BuildingManager.BUILDINGGRID_CELL_SIZE / 2f)) + 1); + int centerI = (int)(refPos.z / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); + int centerJ = (int)(refPos.x / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); + int radius = Math.Max(1, (int)(maxBuildingDistance / ((float)BuildingManager.BUILDINGGRID_CELL_SIZE / 2f)) + 1); - BuildingManager buildingMan = Singleton.instance; - Randomizer rng = Singleton.instance.m_randomizer; + BuildingManager buildingMan = Singleton.instance; + Randomizer rng = Singleton.instance.m_randomizer; - ushort foundBuildingId = 0; - Vector3 myParkPos = parkPos; - Quaternion myParkRot = parkRot; - float myParkOffset = parkOffset; + ushort foundBuildingId = 0; + Vector3 myParkPos = parkPos; + Quaternion myParkRot = parkRot; + float myParkOffset = parkOffset; - LoopUtil.SpiralLoop(centerI, centerJ, radius, radius, delegate (int i, int j) { - if (i < 0 || i >= BuildingManager.BUILDINGGRID_RESOLUTION || j < 0 || j >= BuildingManager.BUILDINGGRID_RESOLUTION) - return true; + LoopUtil.SpiralLoop(centerI, centerJ, radius, radius, delegate (int i, int j) { + if (i < 0 || i >= BuildingManager.BUILDINGGRID_RESOLUTION || j < 0 || j >= BuildingManager.BUILDINGGRID_RESOLUTION) + return true; #if DEBUG - if (debug) { - //Log._Debug($"FindParkingSpaceBuilding: Checking building grid @ i={i}, j={j}, index={i * BuildingManager.BUILDINGGRID_RESOLUTION + j} for {refPos}, homeID {homeID}, segment {segmentId}, maxDistance {maxDistance}"); - } + if (debug) { + //Log._Debug($"FindParkingSpaceBuilding: Checking building grid @ i={i}, j={j}, index={i * BuildingManager.BUILDINGGRID_RESOLUTION + j} for {refPos}, homeID {homeID}, segment {segmentId}, maxDistance {maxDistance}"); + } #endif - ushort buildingId = buildingMan.m_buildingGrid[i * BuildingManager.BUILDINGGRID_RESOLUTION + j]; - int numIterations = 0; - while (buildingId != 0) { - Vector3 innerParkPos; Quaternion innerParkRot; float innerParkOffset; + ushort buildingId = buildingMan.m_buildingGrid[i * BuildingManager.BUILDINGGRID_RESOLUTION + j]; + int numIterations = 0; + while (buildingId != 0) { + Vector3 innerParkPos; Quaternion innerParkRot; float innerParkOffset; #if DEBUG - if (debug) { - //Log._Debug($"FindParkingSpaceBuilding: Checking building {buildingId} @ i={i}, j={j}, index={i * BuildingManager.BUILDINGGRID_RESOLUTION + j}, for {refPos}, homeID {homeID}, segment {segmentId}, maxDistance {maxDistance}."); - } + if (debug) { + //Log._Debug($"FindParkingSpaceBuilding: Checking building {buildingId} @ i={i}, j={j}, index={i * BuildingManager.BUILDINGGRID_RESOLUTION + j}, for {refPos}, homeID {homeID}, segment {segmentId}, maxDistance {maxDistance}."); + } #endif - if (FindParkingSpacePropAtBuilding(vehicleInfo, homeID, ignoreParked, buildingId, ref buildingMan.m_buildings.m_buffer[(int)buildingId], segmentId, refPos, ref maxParkingSpaceDistance, randomize, out innerParkPos, out innerParkRot, out innerParkOffset)) { + if (FindParkingSpacePropAtBuilding(vehicleInfo, homeID, ignoreParked, buildingId, ref buildingMan.m_buildings.m_buffer[(int)buildingId], segmentId, refPos, ref maxParkingSpaceDistance, randomize, out innerParkPos, out innerParkRot, out innerParkOffset)) { #if DEBUG - /*/if (fineDebug && homeID != 0) - Log._Debug($"FindParkingSpaceBuilding: Found a parking space for {refPos}, homeID {homeID} @ building {buildingId}, {myParkPos}, offset {myParkOffset}!"); - */ + /*/if (fineDebug && homeID != 0) + Log._Debug($"FindParkingSpaceBuilding: Found a parking space for {refPos}, homeID {homeID} @ building {buildingId}, {myParkPos}, offset {myParkOffset}!"); + */ #endif - foundBuildingId = buildingId; - myParkPos = innerParkPos; - myParkRot = innerParkRot; - myParkOffset = innerParkOffset; + foundBuildingId = buildingId; + myParkPos = innerParkPos; + myParkRot = innerParkRot; + myParkOffset = innerParkOffset; - if (!randomize || rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) - return false; - } - buildingId = buildingMan.m_buildings.m_buffer[(int)buildingId].m_nextGridBuilding; - if (++numIterations >= 49152) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } + if (!randomize || rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) + return false; + } + buildingId = buildingMan.m_buildings.m_buffer[(int)buildingId].m_nextGridBuilding; + if (++numIterations >= 49152) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } - return true; - }); + return true; + }); - if (foundBuildingId == 0) { + if (foundBuildingId == 0) { #if DEBUG - if (debug && homeID != 0) - Log._Debug($"FindParkingSpaceBuilding: Could not find a parking space for homeID {homeID}!"); + if (debug && homeID != 0) + Log._Debug($"FindParkingSpaceBuilding: Could not find a parking space for homeID {homeID}!"); #endif - return 0; - } + return 0; + } - parkPos = myParkPos; - parkRot = myParkRot; - parkOffset = myParkOffset; + parkPos = myParkPos; + parkRot = myParkRot; + parkOffset = myParkOffset; - return foundBuildingId; - } + return foundBuildingId; + } - public bool FindParkingSpacePropAtBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort buildingID, ref Building building, ushort segmentId, Vector3 refPos, ref float maxDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { + public bool FindParkingSpacePropAtBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort buildingID, ref Building building, ushort segmentId, Vector3 refPos, ref float maxDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[22]; + bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif - int buildingWidth = building.Width; - int buildingLength = building.Length; + int buildingWidth = building.Width; + int buildingLength = building.Length; - // NON-STOCK CODE START - parkOffset = -1f; // only set if segmentId != 0 - parkPos = default(Vector3); - parkRot = default(Quaternion); + // NON-STOCK CODE START + parkOffset = -1f; // only set if segmentId != 0 + parkPos = default(Vector3); + parkRot = default(Quaternion); - if ((building.m_flags & Building.Flags.Created) == Building.Flags.None) { + if ((building.m_flags & Building.Flags.Created) == Building.Flags.None) { #if DEBUG - if (debug) - Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not created."); + if (debug) + Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not created."); #endif - return false; - } + return false; + } - if ((building.m_problems & Notification.Problem.TurnedOff) != Notification.Problem.None) { + if ((building.m_problems & Notification.Problem.TurnedOff) != Notification.Problem.None) { #if DEBUG - if (debug) - Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not active."); + if (debug) + Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not active."); #endif - return false; - } + return false; + } - if ((building.m_flags & Building.Flags.Collapsed) != Building.Flags.None) { + if ((building.m_flags & Building.Flags.Collapsed) != Building.Flags.None) { #if DEBUG - if (debug) - Log._Debug($"Refusing to find parking space at building {buildingID}! Building is collapsed."); + if (debug) + Log._Debug($"Refusing to find parking space at building {buildingID}! Building is collapsed."); #endif - return false; - } + return false; + } - Randomizer rng = Singleton.instance.m_randomizer; // NON-STOCK CODE + Randomizer rng = Singleton.instance.m_randomizer; // NON-STOCK CODE - bool isElectric = vehicleInfo.m_class.m_subService != ItemClass.SubService.ResidentialLow; - BuildingInfo buildingInfo = building.Info; - Matrix4x4 transformMatrix = default(Matrix4x4); - bool transformMatrixCalculated = false; - bool result = false; - if (buildingInfo.m_class.m_service == ItemClass.Service.Residential && buildingID != homeID && rng.Int32((uint)Options.getRecklessDriverModulo()) != 0) { // NON-STOCK CODE + bool isElectric = vehicleInfo.m_class.m_subService != ItemClass.SubService.ResidentialLow; + BuildingInfo buildingInfo = building.Info; + Matrix4x4 transformMatrix = default(Matrix4x4); + bool transformMatrixCalculated = false; + bool result = false; + if (buildingInfo.m_class.m_service == ItemClass.Service.Residential && buildingID != homeID && rng.Int32((uint)Options.getRecklessDriverModulo()) != 0) { // NON-STOCK CODE #if DEBUG - /*if (fineDebug) - Log._Debug($"Refusing to find parking space at building {buildingID}! Building is a residential building which does not match home id {homeID}.");*/ + /*if (fineDebug) + Log._Debug($"Refusing to find parking space at building {buildingID}! Building is a residential building which does not match home id {homeID}.");*/ #endif - return false; - } + return false; + } - float propMinDistance = 9999f; // NON-STOCK CODE - if (buildingInfo.m_props != null && (buildingInfo.m_hasParkingSpaces & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) { - for (int i = 0; i < buildingInfo.m_props.Length; i++) { - BuildingInfo.Prop prop = buildingInfo.m_props[i]; - Randomizer randomizer = new Randomizer((int)buildingID << 6 | prop.m_index); - if (randomizer.Int32(100u) < prop.m_probability && buildingLength >= prop.m_requiredLength) { - PropInfo propInfo = prop.m_finalProp; - if (propInfo != null) { - propInfo = propInfo.GetVariation(ref randomizer); - if (propInfo.m_parkingSpaces != null && propInfo.m_parkingSpaces.Length != 0) { - if (!transformMatrixCalculated) { - transformMatrixCalculated = true; - Vector3 pos = Building.CalculateMeshPosition(buildingInfo, building.m_position, building.m_angle, building.Length); - Quaternion q = Quaternion.AngleAxis(building.m_angle * 57.29578f, Vector3.down); - transformMatrix.SetTRS(pos, q, Vector3.one); - } - Vector3 position = transformMatrix.MultiplyPoint(prop.m_position); - if (CustomPassengerCarAI.FindParkingSpaceProp(isElectric, ignoreParked, propInfo, position, building.m_angle + prop.m_radAngle, prop.m_fixedHeight, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, ref propMinDistance, ref parkPos, ref parkRot)) { // NON-STOCK CODE - result = true; - if (randomize && propMinDistance <= maxDistance && rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) == 0) - break; - } - } - } - } - } - } + float propMinDistance = 9999f; // NON-STOCK CODE + if (buildingInfo.m_props != null && (buildingInfo.m_hasParkingSpaces & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) { + for (int i = 0; i < buildingInfo.m_props.Length; i++) { + BuildingInfo.Prop prop = buildingInfo.m_props[i]; + Randomizer randomizer = new Randomizer((int)buildingID << 6 | prop.m_index); + if (randomizer.Int32(100u) < prop.m_probability && buildingLength >= prop.m_requiredLength) { + PropInfo propInfo = prop.m_finalProp; + if (propInfo != null) { + propInfo = propInfo.GetVariation(ref randomizer); + if (propInfo.m_parkingSpaces != null && propInfo.m_parkingSpaces.Length != 0) { + if (!transformMatrixCalculated) { + transformMatrixCalculated = true; + Vector3 pos = Building.CalculateMeshPosition(buildingInfo, building.m_position, building.m_angle, building.Length); + Quaternion q = Quaternion.AngleAxis(building.m_angle * 57.29578f, Vector3.down); + transformMatrix.SetTRS(pos, q, Vector3.one); + } + Vector3 position = transformMatrix.MultiplyPoint(prop.m_position); + if (CustomPassengerCarAI.FindParkingSpaceProp(isElectric, ignoreParked, propInfo, position, building.m_angle + prop.m_radAngle, prop.m_fixedHeight, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, ref propMinDistance, ref parkPos, ref parkRot)) { // NON-STOCK CODE + result = true; + if (randomize && propMinDistance <= maxDistance && rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) == 0) + break; + } + } + } + } + } + } - if (result && propMinDistance <= maxDistance) { - maxDistance = propMinDistance; // NON-STOCK CODE + if (result && propMinDistance <= maxDistance) { + maxDistance = propMinDistance; // NON-STOCK CODE #if DEBUG - if (debug) - Log._Debug($"Found parking space prop in range ({maxDistance}) at building {buildingID}."); + if (debug) + Log._Debug($"Found parking space prop in range ({maxDistance}) at building {buildingID}."); #endif - if (segmentId != 0) { - // check if building is accessible from the given segment + if (segmentId != 0) { + // check if building is accessible from the given segment #if DEBUG - if (debug) - Log._Debug($"Calculating unspawn position of building {buildingID} for segment {segmentId}."); + if (debug) + Log._Debug($"Calculating unspawn position of building {buildingID} for segment {segmentId}."); #endif - Vector3 unspawnPos; - Vector3 unspawnTargetPos; - building.Info.m_buildingAI.CalculateUnspawnPosition(buildingID, ref building, ref Singleton.instance.m_randomizer, vehicleInfo, out unspawnPos, out unspawnTargetPos); - - Vector3 lanePos; - uint laneId; - int laneIndex; - float laneOffset; - // calculate segment offset - if (Singleton.instance.m_segments.m_buffer[segmentId].GetClosestLanePosition(unspawnPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out lanePos, out laneId, out laneIndex, out laneOffset)) { -#if DEBUG - if (debug) - Log._Debug($"Succeeded in finding unspawn position lane offset for building {buildingID}, segment {segmentId}, unspawnPos={unspawnPos}! lanePos={lanePos}, dist={(lanePos - unspawnPos).magnitude}, laneId={laneId}, laneIndex={laneIndex}, laneOffset={laneOffset}"); -#endif - - /*if (dist > 16f) { - if (debug) - Log._Debug($"Distance between unspawn position and lane position is too big! {dist} unspawnPos={unspawnPos} lanePos={lanePos}"); - return false; - }*/ - - parkOffset = laneOffset; - } else { -#if DEBUG - if (debug) - Log.Warning($"Could not find unspawn position lane offset for building {buildingID}, segment {segmentId}, unspawnPos={unspawnPos}!"); -#endif - } - } - - return true; - } else { -#if DEBUG - if (result && debug) - Log._Debug($"Could not find parking space prop in range ({maxDistance}) at building {buildingID}."); -#endif - return false; - } - } - - public bool FindParkingSpaceRoadSideForVehiclePos(VehicleInfo vehicleInfo, ushort ignoreParked, ushort segmentId, Vector3 refPos, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset, out uint laneId, out int laneIndex) { -#if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[22]; -#endif - - float width = vehicleInfo.m_generatedInfo.m_size.x; - float length = vehicleInfo.m_generatedInfo.m_size.z; - - NetManager netManager = Singleton.instance; - if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) != NetSegment.Flags.None) { - if (netManager.m_segments.m_buffer[segmentId].GetClosestLanePosition(refPos, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out parkPos, out laneId, out laneIndex, out parkOffset)) { - if (!Options.parkingRestrictionsEnabled || ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, netManager.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex].m_finalDirection)) { - if (CustomPassengerCarAI.FindParkingSpaceRoadSide(ignoreParked, segmentId, parkPos, width, length, out parkPos, out parkRot, out parkOffset)) { -#if DEBUG - if (debug) - Log._Debug($"FindParkingSpaceRoadSideForVehiclePos: Found a parking space for refPos {refPos} @ {parkPos}, laneId {laneId}, laneIndex {laneIndex}!"); -#endif - return true; - } - } - } - } - - // - - parkPos = default(Vector3); - parkRot = default(Quaternion); - laneId = 0; - laneIndex = -1; - parkOffset = -1f; - return false; - } - - public bool FindParkingSpaceRoadSide(ushort ignoreParked, Vector3 refPos, float width, float length, float maxDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { - return FindParkingSpaceAtRoadSide(ignoreParked, refPos, width, length, maxDistance, false, out parkPos, out parkRot, out parkOffset) != 0; - } - - public bool FindParkingSpaceBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort segmentId, Vector3 refPos, float maxBuildingDistance, float maxParkingSpaceDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { - return FindParkingSpaceBuilding(vehicleInfo, homeID, ignoreParked, segmentId, refPos, maxBuildingDistance, maxParkingSpaceDistance, false, out parkPos, out parkRot, out parkOffset) != 0; - } - - public bool GetBuildingInfoViewColor(ushort buildingId, ref Building buildingData, ref ExtBuilding extBuilding, InfoManager.InfoMode infoMode, out Color? color) { - color = null; - - if (infoMode == InfoManager.InfoMode.Traffic) { - // parking space demand info view - color = Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_targetColor, Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_negativeColor, Mathf.Clamp01((float)extBuilding.parkingSpaceDemand * 0.01f)); - return true; - } else if (infoMode == InfoManager.InfoMode.Transport && !(buildingData.Info.m_buildingAI is DepotAI)) { - // public transport demand info view - // TODO should not depend on UI class "TrafficManagerTool" - color = Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)InfoManager.InfoMode.Traffic].m_targetColor, Singleton.instance.m_properties.m_modeProperties[(int)InfoManager.InfoMode.Traffic].m_negativeColor, Mathf.Clamp01((float)(TrafficManagerTool.CurrentTransportDemandViewMode == TransportDemandViewMode.Outgoing ? extBuilding.outgoingPublicTransportDemand : extBuilding.incomingPublicTransportDemand) * 0.01f)); - return true; - } + Vector3 unspawnPos; + Vector3 unspawnTargetPos; + building.Info.m_buildingAI.CalculateUnspawnPosition(buildingID, ref building, ref Singleton.instance.m_randomizer, vehicleInfo, out unspawnPos, out unspawnTargetPos); - return false; - } - - public string EnrichLocalizedCitizenStatus(string ret, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen) { - //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - //if (extCitInstMan.IsValid(extInstance.instanceId)) { - switch (extInstance.pathMode) { - case ExtPathMode.ApproachingParkedCar: - case ExtPathMode.RequiresCarPath: - case ExtPathMode.RequiresMixedCarPathToTarget: - ret = Translation.GetString("Entering_vehicle") + ", " + ret; - break; - case ExtPathMode.RequiresWalkingPathToParkedCar: - case ExtPathMode.CalculatingWalkingPathToParkedCar: - case ExtPathMode.WalkingToParkedCar: - ret = Translation.GetString("Walking_to_car") + ", " + ret; - break; - case ExtPathMode.CalculatingWalkingPathToTarget: - case ExtPathMode.TaxiToTarget: - case ExtPathMode.WalkingToTarget: - if ((extCitizen.transportMode & ExtTransportMode.PublicTransport) != ExtTransportMode.None) { - ret = Translation.GetString("Using_public_transport") + ", " + ret; - } else { - ret = Translation.GetString("Walking") + ", " + ret; - } - break; - case ExtPathMode.CalculatingCarPathToTarget: - case ExtPathMode.CalculatingCarPathToKnownParkPos: - ret = Translation.GetString("Thinking_of_a_good_parking_spot") + ", " + ret; - break; - } - //} - return ret; - } - - public string EnrichLocalizedCarStatus(string ret, ref ExtCitizenInstance driverExtInstance) { - //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - //if (extCitInstMan.IsValid(driverExtInstance.instanceId)) { - switch (driverExtInstance.pathMode) { - case ExtPathMode.DrivingToAltParkPos: - if (driverExtInstance.failedParkingAttempts <= 1) { - ret = Translation.GetString("Driving_to_a_parking_spot") + ", " + ret; - } else { - ret = Translation.GetString("Driving_to_another_parking_spot") + " (#" + driverExtInstance.failedParkingAttempts + "), " + ret; - } - break; - case ExtPathMode.CalculatingCarPathToKnownParkPos: - case ExtPathMode.DrivingToKnownParkPos: - ret = Translation.GetString("Driving_to_a_parking_spot") + ", " + ret; - break; - case ExtPathMode.ParkingFailed: - case ExtPathMode.CalculatingCarPathToAltParkPos: - ret = Translation.GetString("Looking_for_a_parking_spot") + ", " + ret; - break; - case ExtPathMode.RequiresWalkingPathToTarget: - ret = Locale.Get("VEHICLE_STATUS_PARKING") + ", " + ret; - break; - } - //} - return ret; - } - } -} + Vector3 lanePos; + uint laneId; + int laneIndex; + float laneOffset; + // calculate segment offset + if (Singleton.instance.m_segments.m_buffer[segmentId].GetClosestLanePosition(unspawnPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out lanePos, out laneId, out laneIndex, out laneOffset)) { +#if DEBUG + if (debug) + Log._Debug($"Succeeded in finding unspawn position lane offset for building {buildingID}, segment {segmentId}, unspawnPos={unspawnPos}! lanePos={lanePos}, dist={(lanePos - unspawnPos).magnitude}, laneId={laneId}, laneIndex={laneIndex}, laneOffset={laneOffset}"); +#endif + + /*if (dist > 16f) { + if (debug) + Log._Debug($"Distance between unspawn position and lane position is too big! {dist} unspawnPos={unspawnPos} lanePos={lanePos}"); + return false; + }*/ + + parkOffset = laneOffset; + } else { +#if DEBUG + if (debug) + Log.Warning($"Could not find unspawn position lane offset for building {buildingID}, segment {segmentId}, unspawnPos={unspawnPos}!"); +#endif + } + } + + return true; + } else { +#if DEBUG + if (result && debug) + Log._Debug($"Could not find parking space prop in range ({maxDistance}) at building {buildingID}."); +#endif + return false; + } + } + + public bool FindParkingSpaceRoadSideForVehiclePos(VehicleInfo vehicleInfo, ushort ignoreParked, ushort segmentId, Vector3 refPos, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset, out uint laneId, out int laneIndex) { +#if DEBUG + bool debug = GlobalConfig.Instance.Debug.Switches[22]; +#endif + + float width = vehicleInfo.m_generatedInfo.m_size.x; + float length = vehicleInfo.m_generatedInfo.m_size.z; + + NetManager netManager = Singleton.instance; + if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) != NetSegment.Flags.None) { + if (netManager.m_segments.m_buffer[segmentId].GetClosestLanePosition(refPos, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out parkPos, out laneId, out laneIndex, out parkOffset)) { + if (!Options.parkingRestrictionsEnabled || ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, netManager.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex].m_finalDirection)) { + if (CustomPassengerCarAI.FindParkingSpaceRoadSide(ignoreParked, segmentId, parkPos, width, length, out parkPos, out parkRot, out parkOffset)) { +#if DEBUG + if (debug) + Log._Debug($"FindParkingSpaceRoadSideForVehiclePos: Found a parking space for refPos {refPos} @ {parkPos}, laneId {laneId}, laneIndex {laneIndex}!"); +#endif + return true; + } + } + } + } + + // + + parkPos = default(Vector3); + parkRot = default(Quaternion); + laneId = 0; + laneIndex = -1; + parkOffset = -1f; + return false; + } + + public bool FindParkingSpaceRoadSide(ushort ignoreParked, Vector3 refPos, float width, float length, float maxDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { + return FindParkingSpaceAtRoadSide(ignoreParked, refPos, width, length, maxDistance, false, out parkPos, out parkRot, out parkOffset) != 0; + } + + public bool FindParkingSpaceBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort segmentId, Vector3 refPos, float maxBuildingDistance, float maxParkingSpaceDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { + return FindParkingSpaceBuilding(vehicleInfo, homeID, ignoreParked, segmentId, refPos, maxBuildingDistance, maxParkingSpaceDistance, false, out parkPos, out parkRot, out parkOffset) != 0; + } + + public bool GetBuildingInfoViewColor(ushort buildingId, ref Building buildingData, ref ExtBuilding extBuilding, InfoManager.InfoMode infoMode, out Color? color) { + color = null; + + if (infoMode == InfoManager.InfoMode.Traffic) { + // parking space demand info view + color = Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_targetColor, Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_negativeColor, Mathf.Clamp01((float)extBuilding.parkingSpaceDemand * 0.01f)); + return true; + } else if (infoMode == InfoManager.InfoMode.Transport && !(buildingData.Info.m_buildingAI is DepotAI)) { + // public transport demand info view + // TODO should not depend on UI class "TrafficManagerTool" + color = Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)InfoManager.InfoMode.Traffic].m_targetColor, Singleton.instance.m_properties.m_modeProperties[(int)InfoManager.InfoMode.Traffic].m_negativeColor, Mathf.Clamp01((float)(TrafficManagerTool.CurrentTransportDemandViewMode == TransportDemandViewMode.Outgoing ? extBuilding.outgoingPublicTransportDemand : extBuilding.incomingPublicTransportDemand) * 0.01f)); + return true; + } + + return false; + } + + public string EnrichLocalizedCitizenStatus(string ret, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen) { + //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + //if (extCitInstMan.IsValid(extInstance.instanceId)) { + switch (extInstance.pathMode) { + case ExtPathMode.ApproachingParkedCar: + case ExtPathMode.RequiresCarPath: + case ExtPathMode.RequiresMixedCarPathToTarget: + ret = Translation.GetString("Entering_vehicle") + ", " + ret; + break; + case ExtPathMode.RequiresWalkingPathToParkedCar: + case ExtPathMode.CalculatingWalkingPathToParkedCar: + case ExtPathMode.WalkingToParkedCar: + ret = Translation.GetString("Walking_to_car") + ", " + ret; + break; + case ExtPathMode.CalculatingWalkingPathToTarget: + case ExtPathMode.TaxiToTarget: + case ExtPathMode.WalkingToTarget: + if ((extCitizen.transportMode & ExtTransportMode.PublicTransport) != ExtTransportMode.None) { + ret = Translation.GetString("Using_public_transport") + ", " + ret; + } else { + ret = Translation.GetString("Walking") + ", " + ret; + } + break; + case ExtPathMode.CalculatingCarPathToTarget: + case ExtPathMode.CalculatingCarPathToKnownParkPos: + ret = Translation.GetString("Thinking_of_a_good_parking_spot") + ", " + ret; + break; + } + //} + return ret; + } + + public string EnrichLocalizedCarStatus(string ret, ref ExtCitizenInstance driverExtInstance) { + //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + //if (extCitInstMan.IsValid(driverExtInstance.instanceId)) { + switch (driverExtInstance.pathMode) { + case ExtPathMode.DrivingToAltParkPos: + if (driverExtInstance.failedParkingAttempts <= 1) { + ret = Translation.GetString("Driving_to_a_parking_spot") + ", " + ret; + } else { + ret = Translation.GetString("Driving_to_another_parking_spot") + " (#" + driverExtInstance.failedParkingAttempts + "), " + ret; + } + break; + case ExtPathMode.CalculatingCarPathToKnownParkPos: + case ExtPathMode.DrivingToKnownParkPos: + ret = Translation.GetString("Driving_to_a_parking_spot") + ", " + ret; + break; + case ExtPathMode.ParkingFailed: + case ExtPathMode.CalculatingCarPathToAltParkPos: + ret = Translation.GetString("Looking_for_a_parking_spot") + ", " + ret; + break; + case ExtPathMode.RequiresWalkingPathToTarget: + ret = Locale.Get("VEHICLE_STATUS_PARKING") + ", " + ret; + break; + } + //} + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs b/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs index 89c062e31..944761478 100644 --- a/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs +++ b/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs @@ -1,292 +1,290 @@ -using System; -using System.Collections.Generic; -using ColossalFramework; -using TrafficManager.Geometry; -using TrafficManager.Util; -using TrafficManager.TrafficLight; -using TrafficManager.State; -using System.Linq; -using TrafficManager.Traffic; -using CSUtil.Commons; -using TrafficManager.TrafficLight.Impl; -using TrafficManager.Geometry.Impl; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - namespace TrafficManager.Manager.Impl { - /// - /// Manages the states of all custom traffic lights on the map - /// - public class CustomSegmentLightsManager : AbstractGeometryObservingManager, ICustomSegmentLightsManager { - public static CustomSegmentLightsManager Instance { get; private set; } = null; - - static CustomSegmentLightsManager() { - Instance = new CustomSegmentLightsManager(); - } - - /// - /// custom traffic lights by segment id - /// - private CustomSegment[] CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"Custom segments:"); - for (int i = 0; i < CustomSegments.Length; ++i) { - if (CustomSegments[i] == null) { - continue; - } - Log._Debug($"Segment {i}: {CustomSegments[i]}"); - } - } - - /// - /// Adds custom traffic lights at the specified node and segment. - /// Light states (red, yellow, green) are taken from the "live" state, that is the traffic light's light state right before the custom light takes control. - /// - /// - /// - public ICustomSegmentLights AddLiveSegmentLights(ushort segmentId, bool startNode) { - if (! Services.NetService.IsSegmentValid(segmentId)) { - return null; - } - - ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, startNode); - uint currentFrameIndex = Services.SimulationService.CurrentFrameIndex; - - RoadBaseAI.TrafficLightState vehicleLightState; - RoadBaseAI.TrafficLightState pedestrianLightState; - bool vehicles; - bool pedestrians; - - RoadBaseAI.GetTrafficLightState(nodeId, ref Singleton.instance.m_segments.m_buffer[segmentId], - currentFrameIndex - 256u, out vehicleLightState, out pedestrianLightState, out vehicles, - out pedestrians); - - return AddSegmentLights(segmentId, startNode, - vehicleLightState == RoadBaseAI.TrafficLightState.Green - ? RoadBaseAI.TrafficLightState.Green - : RoadBaseAI.TrafficLightState.Red); - } - - /// - /// Adds custom traffic lights at the specified node and segment. - /// Light stats are set to the given light state, or to "Red" if no light state is given. - /// - /// - /// - /// (optional) light state to set - public ICustomSegmentLights AddSegmentLights(ushort segmentId, bool startNode, RoadBaseAI.TrafficLightState lightState=RoadBaseAI.TrafficLightState.Red) { + using System.Collections.Generic; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Traffic.Data; + using Traffic.Enums; + using TrafficLight; + using TrafficLight.Impl; + using ExtVehicleType = global::TrafficManager.Traffic.ExtVehicleType; + + /// + /// Manages the states of all custom traffic lights on the map + /// + public class CustomSegmentLightsManager : AbstractGeometryObservingManager, ICustomSegmentLightsManager { + public static CustomSegmentLightsManager Instance { get; private set; } = null; + + static CustomSegmentLightsManager() { + Instance = new CustomSegmentLightsManager(); + } + + /// + /// custom traffic lights by segment id + /// + private CustomSegment[] CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"Custom segments:"); + for (int i = 0; i < CustomSegments.Length; ++i) { + if (CustomSegments[i] == null) { + continue; + } + Log._Debug($"Segment {i}: {CustomSegments[i]}"); + } + } + + /// + /// Adds custom traffic lights at the specified node and segment. + /// Light states (red, yellow, green) are taken from the "live" state, that is the traffic light's light state right before the custom light takes control. + /// + /// + /// + public ICustomSegmentLights AddLiveSegmentLights(ushort segmentId, bool startNode) { + if (! Services.NetService.IsSegmentValid(segmentId)) { + return null; + } + + ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, startNode); + uint currentFrameIndex = Services.SimulationService.CurrentFrameIndex; + + RoadBaseAI.TrafficLightState vehicleLightState; + RoadBaseAI.TrafficLightState pedestrianLightState; + bool vehicles; + bool pedestrians; + + RoadBaseAI.GetTrafficLightState(nodeId, ref Singleton.instance.m_segments.m_buffer[segmentId], + currentFrameIndex - 256u, out vehicleLightState, out pedestrianLightState, out vehicles, + out pedestrians); + + return AddSegmentLights(segmentId, startNode, + vehicleLightState == RoadBaseAI.TrafficLightState.Green + ? RoadBaseAI.TrafficLightState.Green + : RoadBaseAI.TrafficLightState.Red); + } + + /// + /// Adds custom traffic lights at the specified node and segment. + /// Light stats are set to the given light state, or to "Red" if no light state is given. + /// + /// + /// + /// (optional) light state to set + public ICustomSegmentLights AddSegmentLights(ushort segmentId, bool startNode, RoadBaseAI.TrafficLightState lightState=RoadBaseAI.TrafficLightState.Red) { #if DEBUG - Log._Trace($"CustomTrafficLights.AddSegmentLights: Adding segment light: {segmentId} @ startNode={startNode}"); + Log._Trace($"CustomTrafficLights.AddSegmentLights: Adding segment light: {segmentId} @ startNode={startNode}"); #endif - if (!Services.NetService.IsSegmentValid(segmentId)) { - return null; - } - - CustomSegment customSegment = CustomSegments[segmentId]; - if (customSegment == null) { - customSegment = new CustomSegment(); - CustomSegments[segmentId] = customSegment; - } else { - ICustomSegmentLights existingLights = startNode ? customSegment.StartNodeLights : customSegment.EndNodeLights; - - if (existingLights != null) { - existingLights.SetLights(lightState); - return existingLights; - } - } - - if (startNode) { - customSegment.StartNodeLights = new CustomSegmentLights(this, segmentId, startNode, false); - customSegment.StartNodeLights.SetLights(lightState); - return customSegment.StartNodeLights; - } else { - customSegment.EndNodeLights = new CustomSegmentLights(this, segmentId, startNode, false); - customSegment.EndNodeLights.SetLights(lightState); - return customSegment.EndNodeLights; - } - } - - public bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights) { - bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); - if (startNode == null) { - return false; - } - - CustomSegment customSegment = CustomSegments[segmentId]; - if (customSegment == null) { - customSegment = new CustomSegment(); - CustomSegments[segmentId] = customSegment; - } - - if ((bool)startNode) { - customSegment.StartNodeLights = lights; - } else { - customSegment.EndNodeLights = lights; - } - return true; - } - - /// - /// Add custom traffic lights at the given node - /// - /// - public void AddNodeLights(ushort nodeId) { - if (! Services.NetService.IsNodeValid(nodeId)) { - return; - } - - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - AddSegmentLights(segmentId, segment.m_startNode == nodeId); - return true; - }); - } - - /// - /// Removes custom traffic lights at the given node - /// - /// - public void RemoveNodeLights(ushort nodeId) { - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - RemoveSegmentLight(segmentId, segment.m_startNode == nodeId); - return true; - }); - } - - /// - /// Removes all custom traffic lights at both ends of the given segment. - /// - /// - public void RemoveSegmentLights(ushort segmentId) { - CustomSegments[segmentId] = null; - } - - /// - /// Removes the custom traffic light at the given segment end. - /// - /// - /// - public void RemoveSegmentLight(ushort segmentId, bool startNode) { + if (!Services.NetService.IsSegmentValid(segmentId)) { + return null; + } + + CustomSegment customSegment = CustomSegments[segmentId]; + if (customSegment == null) { + customSegment = new CustomSegment(); + CustomSegments[segmentId] = customSegment; + } else { + ICustomSegmentLights existingLights = startNode ? customSegment.StartNodeLights : customSegment.EndNodeLights; + + if (existingLights != null) { + existingLights.SetLights(lightState); + return existingLights; + } + } + + if (startNode) { + customSegment.StartNodeLights = new CustomSegmentLights(this, segmentId, startNode, false); + customSegment.StartNodeLights.SetLights(lightState); + return customSegment.StartNodeLights; + } else { + customSegment.EndNodeLights = new CustomSegmentLights(this, segmentId, startNode, false); + customSegment.EndNodeLights.SetLights(lightState); + return customSegment.EndNodeLights; + } + } + + public bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights) { + bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); + if (startNode == null) { + return false; + } + + CustomSegment customSegment = CustomSegments[segmentId]; + if (customSegment == null) { + customSegment = new CustomSegment(); + CustomSegments[segmentId] = customSegment; + } + + if ((bool)startNode) { + customSegment.StartNodeLights = lights; + } else { + customSegment.EndNodeLights = lights; + } + return true; + } + + /// + /// Add custom traffic lights at the given node + /// + /// + public void AddNodeLights(ushort nodeId) { + if (! Services.NetService.IsNodeValid(nodeId)) { + return; + } + + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + AddSegmentLights(segmentId, segment.m_startNode == nodeId); + return true; + }); + } + + /// + /// Removes custom traffic lights at the given node + /// + /// + public void RemoveNodeLights(ushort nodeId) { + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + RemoveSegmentLight(segmentId, segment.m_startNode == nodeId); + return true; + }); + } + + /// + /// Removes all custom traffic lights at both ends of the given segment. + /// + /// + public void RemoveSegmentLights(ushort segmentId) { + CustomSegments[segmentId] = null; + } + + /// + /// Removes the custom traffic light at the given segment end. + /// + /// + /// + public void RemoveSegmentLight(ushort segmentId, bool startNode) { #if DEBUG - Log._Trace($"Removing segment light: {segmentId} @ startNode={startNode}"); + Log._Trace($"Removing segment light: {segmentId} @ startNode={startNode}"); #endif - CustomSegment customSegment = CustomSegments[segmentId]; - if (customSegment == null) { - return; - } - - if (startNode) { - customSegment.StartNodeLights = null; - } else { - customSegment.EndNodeLights = null; - } - - if (customSegment.StartNodeLights == null && customSegment.EndNodeLights == null) { - CustomSegments[segmentId] = null; - } - } - - /// - /// Checks if a custom traffic light is present at the given segment end. - /// - /// - /// - /// - public bool IsSegmentLight(ushort segmentId, bool startNode) { - CustomSegment customSegment = CustomSegments[segmentId]; - if (customSegment == null) { - return false; - } - - return (startNode && customSegment.StartNodeLights != null) || (!startNode && customSegment.EndNodeLights != null); - } - - /// - /// Retrieves the custom traffic light at the given segment end. If none exists, a new custom traffic light is created and returned. - /// - /// - /// - /// existing or new custom traffic light at segment end - public ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode) { - if (! IsSegmentLight(segmentId, startNode)) - return AddLiveSegmentLights(segmentId, startNode); - - return GetSegmentLights(segmentId, startNode); - } - - /// - /// Retrieves the custom traffic light at the given segment end. - /// - /// - /// - /// existing custom traffic light at segment end, null if none exists - public ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add=true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red) { - if (!IsSegmentLight(segmentId, startNode)) { - if (add) - return AddSegmentLights(segmentId, startNode, lightState); - else - return null; - } - - CustomSegment customSegment = CustomSegments[segmentId]; - - if (startNode) { - return customSegment.StartNodeLights; - } else { - return customSegment.EndNodeLights; - } - } - - public void SetLightMode(ushort segmentId, bool startNode, ExtVehicleType vehicleType, LightMode mode) { - ICustomSegmentLights liveLights = GetSegmentLights(segmentId, startNode); - if (liveLights == null) { - Log.Warning($"CustomSegmentLightsManager.SetLightMode({segmentId}, {startNode}, {vehicleType}, {mode}): Could not retrieve segment lights."); - return; - } - ICustomSegmentLight liveLight = liveLights.GetCustomLight(vehicleType); - if (liveLight == null) { - Log.Error($"CustomSegmentLightsManager.SetLightMode: Cannot change light mode on seg. {segmentId} @ {startNode} for vehicle type {vehicleType} to {mode}: Vehicle light not found"); - return; - } - liveLight.CurrentMode = mode; - } - - public bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights) { - ICustomSegmentLights sourceLights = GetSegmentLights(segmentId, startNode); - if (sourceLights == null) { - Log.Warning($"CustomSegmentLightsManager.ApplyLightModes({segmentId}, {startNode}, {otherLights}): Could not retrieve segment lights."); - return false; - } - - foreach (KeyValuePair e in sourceLights.CustomLights) { - ExtVehicleType vehicleType = e.Key; - ICustomSegmentLight targetLight = e.Value; - - ICustomSegmentLight sourceLight; - if (otherLights.CustomLights.TryGetValue(vehicleType, out sourceLight)) { - targetLight.CurrentMode = sourceLight.CurrentMode; - } - } - return true; - } - - public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId) { - bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); - if (startNode == null) { - return null; - } - return GetSegmentLights(segmentId, (bool)startNode, false); - } - - protected override void HandleInvalidSegment(ref ExtSegment seg) { - RemoveSegmentLights(seg.segmentId); - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; - } - } -} + CustomSegment customSegment = CustomSegments[segmentId]; + if (customSegment == null) { + return; + } + + if (startNode) { + customSegment.StartNodeLights = null; + } else { + customSegment.EndNodeLights = null; + } + + if (customSegment.StartNodeLights == null && customSegment.EndNodeLights == null) { + CustomSegments[segmentId] = null; + } + } + + /// + /// Checks if a custom traffic light is present at the given segment end. + /// + /// + /// + /// + public bool IsSegmentLight(ushort segmentId, bool startNode) { + CustomSegment customSegment = CustomSegments[segmentId]; + if (customSegment == null) { + return false; + } + + return (startNode && customSegment.StartNodeLights != null) || (!startNode && customSegment.EndNodeLights != null); + } + + /// + /// Retrieves the custom traffic light at the given segment end. If none exists, a new custom traffic light is created and returned. + /// + /// + /// + /// existing or new custom traffic light at segment end + public ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode) { + if (! IsSegmentLight(segmentId, startNode)) + return AddLiveSegmentLights(segmentId, startNode); + + return GetSegmentLights(segmentId, startNode); + } + + /// + /// Retrieves the custom traffic light at the given segment end. + /// + /// + /// + /// existing custom traffic light at segment end, null if none exists + public ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add=true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red) { + if (!IsSegmentLight(segmentId, startNode)) { + if (add) + return AddSegmentLights(segmentId, startNode, lightState); + else + return null; + } + + CustomSegment customSegment = CustomSegments[segmentId]; + + if (startNode) { + return customSegment.StartNodeLights; + } else { + return customSegment.EndNodeLights; + } + } + + public void SetLightMode(ushort segmentId, + bool startNode, + API.Traffic.Enums.ExtVehicleType vehicleType, + LightMode mode) { + ICustomSegmentLights liveLights = GetSegmentLights(segmentId, startNode); + if (liveLights == null) { + Log.Warning($"CustomSegmentLightsManager.SetLightMode({segmentId}, {startNode}, {vehicleType}, {mode}): Could not retrieve segment lights."); + return; + } + ICustomSegmentLight liveLight = liveLights.GetCustomLight(vehicleType); + if (liveLight == null) { + Log.Error($"CustomSegmentLightsManager.SetLightMode: Cannot change light mode on seg. {segmentId} @ {startNode} for vehicle type {vehicleType} to {mode}: Vehicle light not found"); + return; + } + liveLight.CurrentMode = mode; + } + + public bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights) { + ICustomSegmentLights sourceLights = GetSegmentLights(segmentId, startNode); + if (sourceLights == null) { + Log.Warning($"CustomSegmentLightsManager.ApplyLightModes({segmentId}, {startNode}, {otherLights}): Could not retrieve segment lights."); + return false; + } + + foreach (var e in sourceLights.CustomLights) { + var vehicleType = e.Key; + var targetLight = e.Value; + + if (otherLights.CustomLights.TryGetValue(vehicleType, out var sourceLight)) { + targetLight.CurrentMode = sourceLight.CurrentMode; + } + } + return true; + } + + public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId) { + bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); + if (startNode == null) { + return null; + } + return GetSegmentLights(segmentId, (bool)startNode, false); + } + + protected override void HandleInvalidSegment(ref ExtSegment seg) { + RemoveSegmentLights(seg.segmentId); + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs b/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs index b5855c9db..274d61ab8 100644 --- a/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs +++ b/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs @@ -1,1133 +1,1129 @@ -using ColossalFramework; -using ColossalFramework.Globalization; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using TrafficManager.Custom.AI; -using TrafficManager.Custom.PathFinding; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using TrafficManager.Util; -using UnityEngine; -using static TrafficManager.Traffic.Data.ExtCitizenInstance; - -namespace TrafficManager.Manager.Impl { - public class ExtCitizenInstanceManager : AbstractCustomManager, ICustomDataManager>, IExtCitizenInstanceManager { - public static ExtCitizenInstanceManager Instance = new ExtCitizenInstanceManager(); - - /// - /// All additional data for citizen instance. Index: citizen instance id - /// - public ExtCitizenInstance[] ExtInstances { get; private set; } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"Extended citizen instance data:"); - for (int i = 0; i < ExtInstances.Length; ++i) { - if (!IsValid((ushort)i)) { - continue; - } - Log._Debug($"Citizen instance {i}: {ExtInstances[i]}"); - } - } - - public void OnReleaseInstance(ushort instanceId) { - Reset(ref ExtInstances[instanceId]); - } - - public void ResetInstance(ushort instanceId) { - Reset(ref ExtInstances[instanceId]); - } - - private ExtCitizenInstanceManager() { - ExtInstances = new ExtCitizenInstance[CitizenManager.MAX_INSTANCE_COUNT]; - for (uint i = 0; i < CitizenManager.MAX_INSTANCE_COUNT; ++i) { - ExtInstances[i] = new ExtCitizenInstance((ushort)i); - } - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - Reset(); - } - - internal void Reset() { - for (int i = 0; i < ExtInstances.Length; ++i) { - Reset(ref ExtInstances[i]); - } - } - - public String GetTouristLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { - if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_CONFUSED"); - } - - CitizenManager instance = Singleton.instance; - uint citizenId = data.m_citizen; - ushort vehicleId = 0; - if (citizenId != 0u) { - vehicleId = instance.m_citizens.m_buffer[citizenId].m_vehicle; - } - - ushort targetBuilding = data.m_targetBuilding; - if (targetBuilding == 0) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_CONFUSED"); - } - - if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) { - if (vehicleId != 0) { - VehicleManager vehManager = Singleton.instance; - VehicleInfo info = vehManager.m_vehicles.m_buffer[vehicleId].Info; - if (info.m_class.m_service == ItemClass.Service.Residential && info.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { - if (info.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId]).Citizen == citizenId) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO"); - } - } else if (info.m_class.m_service == ItemClass.Service.PublicTransport || info.m_class.m_service == ItemClass.Service.Disaster) { - ushort transportLine = Singleton.instance.m_nodes.m_buffer[targetBuilding].m_transportLine; - if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); - } - if (vehManager.m_vehicles.m_buffer[vehicleId].m_transportLine != transportLine) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - } - - if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); - } - - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO"); - } - - bool isOutsideConnection = (Singleton.instance.m_buildings.m_buffer[(int)targetBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None; - bool hangsAround = data.m_path == 0u && (data.m_flags & CitizenInstance.Flags.HangAround) != CitizenInstance.Flags.None; - - if (vehicleId != 0) { - VehicleManager vehManager = Singleton.instance; - VehicleInfo vehicleInfo = vehManager.m_vehicles.m_buffer[(int)vehicleId].Info; - if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { - if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[(int)vehicleId]).Citizen == citizenId) { - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); - } - - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO"); - } - } else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) { - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); - } - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); - } - - if (hangsAround) { - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_VISITING"); - } - - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO"); - } - - public String GetResidentLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { - if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_CONFUSED"); - } - - CitizenManager citMan = Singleton.instance; - uint citizenId = data.m_citizen; - bool isStudent = false; - ushort homeId = 0; - ushort workId = 0; - ushort vehicleId = 0; - if (citizenId != 0u) { - homeId = citMan.m_citizens.m_buffer[citizenId].m_homeBuilding; - workId = citMan.m_citizens.m_buffer[citizenId].m_workBuilding; - vehicleId = citMan.m_citizens.m_buffer[citizenId].m_vehicle; - isStudent = ((citMan.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Student) != Citizen.Flags.None); - } - ushort targetBuilding = data.m_targetBuilding; - if (targetBuilding == 0) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_CONFUSED"); - } - - if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None) { - if (vehicleId != 0) { - VehicleManager vehManager = Singleton.instance; - VehicleInfo vehicleInfo = vehManager.m_vehicles.m_buffer[vehicleId].Info; - if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { - if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId]).Citizen == citizenId) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO"); - } - } else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) { - ushort transportLine = Singleton.instance.m_nodes.m_buffer[targetBuilding].m_transportLine; - if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); - } - - if (vehManager.m_vehicles.m_buffer[vehicleId].m_transportLine != transportLine) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - } - - if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); - } - - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO"); - } - - bool isOutsideConnection = (Singleton.instance.m_buildings.m_buffer[(int)targetBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None; - bool hangsAround = data.m_path == 0u && (data.m_flags & CitizenInstance.Flags.HangAround) != CitizenInstance.Flags.None; - if (vehicleId != 0) { - VehicleManager vehicleMan = Singleton.instance; - VehicleInfo vehicleInfo = vehicleMan.m_vehicles.m_buffer[(int)vehicleId].Info; - if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { - if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehicleMan.m_vehicles.m_buffer[(int)vehicleId]).Citizen == citizenId) { - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); - } - - if (targetBuilding == homeId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO_HOME"); - } else if (targetBuilding == workId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_DRIVINGTO_WORK" : "CITIZEN_STATUS_DRIVINGTO_SCHOOL"); - } else { - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO"); - } - } - } else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) { - if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != CitizenInstance.Flags.None) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); - } - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); - } - if (targetBuilding == homeId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_HOME"); - } - if (targetBuilding == workId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_TRAVELLINGTO_WORK" : "CITIZEN_STATUS_TRAVELLINGTO_SCHOOL"); - } - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); - } - - if (targetBuilding == homeId) { - if (hangsAround) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_AT_HOME"); - } - - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO_HOME"); - } else if (targetBuilding == workId) { - if (hangsAround) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_AT_WORK" : "CITIZEN_STATUS_AT_SCHOOL"); - } - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_GOINGTO_WORK" : "CITIZEN_STATUS_GOINGTO_SCHOOL"); - } else { - if (hangsAround) { - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_VISITING"); - } - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO"); - } - } - - public bool StartPathFind(ushort instanceID, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, Vector3 startPos, Vector3 endPos, VehicleInfo vehicleInfo, bool enableTransport, bool ignoreCost) { +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Globalization; + using CSUtil.Commons; + using Custom.PathFinding; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + + public class ExtCitizenInstanceManager : AbstractCustomManager, ICustomDataManager>, IExtCitizenInstanceManager { + public static ExtCitizenInstanceManager Instance = new ExtCitizenInstanceManager(); + + /// + /// All additional data for citizen instance. Index: citizen instance id + /// + public ExtCitizenInstance[] ExtInstances { get; private set; } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"Extended citizen instance data:"); + for (int i = 0; i < ExtInstances.Length; ++i) { + if (!IsValid((ushort)i)) { + continue; + } + Log._Debug($"Citizen instance {i}: {ExtInstances[i]}"); + } + } + + public void OnReleaseInstance(ushort instanceId) { + Reset(ref ExtInstances[instanceId]); + } + + public void ResetInstance(ushort instanceId) { + Reset(ref ExtInstances[instanceId]); + } + + private ExtCitizenInstanceManager() { + ExtInstances = new ExtCitizenInstance[CitizenManager.MAX_INSTANCE_COUNT]; + for (uint i = 0; i < CitizenManager.MAX_INSTANCE_COUNT; ++i) { + ExtInstances[i] = new ExtCitizenInstance((ushort)i); + } + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + Reset(); + } + + internal void Reset() { + for (int i = 0; i < ExtInstances.Length; ++i) { + Reset(ref ExtInstances[i]); + } + } + + public String GetTouristLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { + if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_CONFUSED"); + } + + CitizenManager instance = Singleton.instance; + uint citizenId = data.m_citizen; + ushort vehicleId = 0; + if (citizenId != 0u) { + vehicleId = instance.m_citizens.m_buffer[citizenId].m_vehicle; + } + + ushort targetBuilding = data.m_targetBuilding; + if (targetBuilding == 0) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_CONFUSED"); + } + + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) { + if (vehicleId != 0) { + VehicleManager vehManager = Singleton.instance; + VehicleInfo info = vehManager.m_vehicles.m_buffer[vehicleId].Info; + if (info.m_class.m_service == ItemClass.Service.Residential && info.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { + if (info.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId]).Citizen == citizenId) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO"); + } + } else if (info.m_class.m_service == ItemClass.Service.PublicTransport || info.m_class.m_service == ItemClass.Service.Disaster) { + ushort transportLine = Singleton.instance.m_nodes.m_buffer[targetBuilding].m_transportLine; + if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); + } + if (vehManager.m_vehicles.m_buffer[vehicleId].m_transportLine != transportLine) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + } + + if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); + } + + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + + bool isOutsideConnection = (Singleton.instance.m_buildings.m_buffer[(int)targetBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None; + bool hangsAround = data.m_path == 0u && (data.m_flags & CitizenInstance.Flags.HangAround) != CitizenInstance.Flags.None; + + if (vehicleId != 0) { + VehicleManager vehManager = Singleton.instance; + VehicleInfo vehicleInfo = vehManager.m_vehicles.m_buffer[(int)vehicleId].Info; + if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { + if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[(int)vehicleId]).Citizen == citizenId) { + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); + } + + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO"); + } + } else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) { + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); + } + + if (hangsAround) { + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_VISITING"); + } + + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + + public String GetResidentLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { + if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_CONFUSED"); + } + + CitizenManager citMan = Singleton.instance; + uint citizenId = data.m_citizen; + bool isStudent = false; + ushort homeId = 0; + ushort workId = 0; + ushort vehicleId = 0; + if (citizenId != 0u) { + homeId = citMan.m_citizens.m_buffer[citizenId].m_homeBuilding; + workId = citMan.m_citizens.m_buffer[citizenId].m_workBuilding; + vehicleId = citMan.m_citizens.m_buffer[citizenId].m_vehicle; + isStudent = ((citMan.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Student) != Citizen.Flags.None); + } + ushort targetBuilding = data.m_targetBuilding; + if (targetBuilding == 0) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_CONFUSED"); + } + + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None) { + if (vehicleId != 0) { + VehicleManager vehManager = Singleton.instance; + VehicleInfo vehicleInfo = vehManager.m_vehicles.m_buffer[vehicleId].Info; + if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { + if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId]).Citizen == citizenId) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO"); + } + } else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) { + ushort transportLine = Singleton.instance.m_nodes.m_buffer[targetBuilding].m_transportLine; + if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); + } + + if (vehManager.m_vehicles.m_buffer[vehicleId].m_transportLine != transportLine) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + } + + if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); + } + + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + + bool isOutsideConnection = (Singleton.instance.m_buildings.m_buffer[(int)targetBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None; + bool hangsAround = data.m_path == 0u && (data.m_flags & CitizenInstance.Flags.HangAround) != CitizenInstance.Flags.None; + if (vehicleId != 0) { + VehicleManager vehicleMan = Singleton.instance; + VehicleInfo vehicleInfo = vehicleMan.m_vehicles.m_buffer[(int)vehicleId].Info; + if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) { + if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehicleMan.m_vehicles.m_buffer[(int)vehicleId]).Citizen == citizenId) { + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); + } + + if (targetBuilding == homeId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO_HOME"); + } else if (targetBuilding == workId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_DRIVINGTO_WORK" : "CITIZEN_STATUS_DRIVINGTO_SCHOOL"); + } else { + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO"); + } + } + } else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) { + if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != CitizenInstance.Flags.None) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); + } + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); + } + if (targetBuilding == homeId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_HOME"); + } + if (targetBuilding == workId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_TRAVELLINGTO_WORK" : "CITIZEN_STATUS_TRAVELLINGTO_SCHOOL"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); + } + + if (targetBuilding == homeId) { + if (hangsAround) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_AT_HOME"); + } + + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO_HOME"); + } else if (targetBuilding == workId) { + if (hangsAround) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_AT_WORK" : "CITIZEN_STATUS_AT_SCHOOL"); + } + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_GOINGTO_WORK" : "CITIZEN_STATUS_GOINGTO_SCHOOL"); + } else { + if (hangsAround) { + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_VISITING"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + } + + public bool StartPathFind(ushort instanceID, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, Vector3 startPos, Vector3 endPos, VehicleInfo vehicleInfo, bool enableTransport, bool ignoreCost) { #if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log.Warning($"CustomCitizenAI.ExtStartPathFind({instanceID}): called for citizen {instanceData.m_citizen}, startPos={startPos}, endPos={endPos}, sourceBuilding={instanceData.m_sourceBuilding}, targetBuilding={instanceData.m_targetBuilding}, pathMode={extInstance.pathMode}, enableTransport={enableTransport}, ignoreCost={ignoreCost}"); + bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (debug) + Log.Warning($"CustomCitizenAI.ExtStartPathFind({instanceID}): called for citizen {instanceData.m_citizen}, startPos={startPos}, endPos={endPos}, sourceBuilding={instanceData.m_sourceBuilding}, targetBuilding={instanceData.m_targetBuilding}, pathMode={extInstance.pathMode}, enableTransport={enableTransport}, ignoreCost={ignoreCost}"); #endif - // NON-STOCK CODE START - CitizenManager citizenManager = Singleton.instance; - ushort parkedVehicleId = citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; - ushort homeId = citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_homeBuilding; - CarUsagePolicy carUsageMode = CarUsagePolicy.Allowed; + // NON-STOCK CODE START + CitizenManager citizenManager = Singleton.instance; + ushort parkedVehicleId = citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; + ushort homeId = citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_homeBuilding; + CarUsagePolicy carUsageMode = CarUsagePolicy.Allowed; #if BENCHMARK using (var bm = new Benchmark(null, "ParkingAI.Preparation")) { #endif - bool startsAtOutsideConnection = false; - if (Options.parkingAI) { - switch (extInstance.pathMode) { - case ExtPathMode.RequiresWalkingPathToParkedCar: - case ExtPathMode.CalculatingWalkingPathToParkedCar: - case ExtPathMode.WalkingToParkedCar: - case ExtPathMode.ApproachingParkedCar: - if (parkedVehicleId == 0) { - /* - * Parked vehicle not present but citizen wants to reach it - * -> Reset path mode - */ + bool startsAtOutsideConnection = false; + if (Options.parkingAI) { + switch (extInstance.pathMode) { + case ExtPathMode.RequiresWalkingPathToParkedCar: + case ExtPathMode.CalculatingWalkingPathToParkedCar: + case ExtPathMode.WalkingToParkedCar: + case ExtPathMode.ApproachingParkedCar: + if (parkedVehicleId == 0) { + /* + * Parked vehicle not present but citizen wants to reach it + * -> Reset path mode + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'."); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'."); #endif - Reset(ref extInstance); - } else { - /* - * Parked vehicle is present and citizen wants to reach it - * -> Prohibit car usage - */ + Reset(ref extInstance); + } else { + /* + * Parked vehicle is present and citizen wants to reach it + * -> Prohibit car usage + */ #if DEBUG - if (fineDebug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'CalculatingWalkingPathToParkedCar'."); + if (fineDebug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'CalculatingWalkingPathToParkedCar'."); #endif - extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar; - carUsageMode = CarUsagePolicy.Forbidden; - } - break; - case ExtPathMode.RequiresWalkingPathToTarget: - case ExtPathMode.CalculatingWalkingPathToTarget: - case ExtPathMode.WalkingToTarget: - /* - * Citizen walks to target - * -> Reset path mode - */ + extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar; + carUsageMode = CarUsagePolicy.Forbidden; + } + break; + case ExtPathMode.RequiresWalkingPathToTarget: + case ExtPathMode.CalculatingWalkingPathToTarget: + case ExtPathMode.WalkingToTarget: + /* + * Citizen walks to target + * -> Reset path mode + */ #if DEBUG - if (fineDebug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'CalculatingWalkingPathToTarget'."); + if (fineDebug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'CalculatingWalkingPathToTarget'."); #endif - extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget; - carUsageMode = CarUsagePolicy.Forbidden; - break; - case ExtPathMode.RequiresCarPath: - case ExtPathMode.RequiresMixedCarPathToTarget: - case ExtPathMode.DrivingToTarget: - case ExtPathMode.DrivingToKnownParkPos: - case ExtPathMode.DrivingToAltParkPos: - case ExtPathMode.CalculatingCarPathToAltParkPos: - case ExtPathMode.CalculatingCarPathToKnownParkPos: - case ExtPathMode.CalculatingCarPathToTarget: - if (parkedVehicleId == 0) { - /* - * Citizen wants to drive to target but parked vehicle is not present - * -> Reset path mode - */ + extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget; + carUsageMode = CarUsagePolicy.Forbidden; + break; + case ExtPathMode.RequiresCarPath: + case ExtPathMode.RequiresMixedCarPathToTarget: + case ExtPathMode.DrivingToTarget: + case ExtPathMode.DrivingToKnownParkPos: + case ExtPathMode.DrivingToAltParkPos: + case ExtPathMode.CalculatingCarPathToAltParkPos: + case ExtPathMode.CalculatingCarPathToKnownParkPos: + case ExtPathMode.CalculatingCarPathToTarget: + if (parkedVehicleId == 0) { + /* + * Citizen wants to drive to target but parked vehicle is not present + * -> Reset path mode + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'."); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'."); #endif - Reset(ref extInstance); - } else { - /* - * Citizen wants to drive to target and parked vehicle is present - * -> Force parked car usage - */ + Reset(ref extInstance); + } else { + /* + * Citizen wants to drive to target and parked vehicle is present + * -> Force parked car usage + */ #if DEBUG - if (fineDebug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'RequiresCarPath'."); + if (fineDebug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'RequiresCarPath'."); #endif - extInstance.pathMode = ExtPathMode.RequiresCarPath; - carUsageMode = CarUsagePolicy.ForcedParked; - startPos = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; // force to start from the parked car - } - break; - default: + extInstance.pathMode = ExtPathMode.RequiresCarPath; + carUsageMode = CarUsagePolicy.ForcedParked; + startPos = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; // force to start from the parked car + } + break; + default: #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'None'."); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'None'."); #endif - Reset(ref extInstance); - break; - } - - startsAtOutsideConnection = Constants.ManagerFactory.ExtCitizenInstanceManager.IsAtOutsideConnection(instanceID, ref instanceData, ref extInstance, startPos); - if (extInstance.pathMode == ExtPathMode.None) { - if ((instanceData.m_flags & CitizenInstance.Flags.OnTour) != CitizenInstance.Flags.None || ignoreCost) { - /* - * Citizen is on a walking tour or is a mascot - * -> Prohibit car usage - */ + Reset(ref extInstance); + break; + } + + startsAtOutsideConnection = Constants.ManagerFactory.ExtCitizenInstanceManager.IsAtOutsideConnection(instanceID, ref instanceData, ref extInstance, startPos); + if (extInstance.pathMode == ExtPathMode.None) { + if ((instanceData.m_flags & CitizenInstance.Flags.OnTour) != CitizenInstance.Flags.None || ignoreCost) { + /* + * Citizen is on a walking tour or is a mascot + * -> Prohibit car usage + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen ignores cost ({ignoreCost}) or is on a walking tour ({(instanceData.m_flags & CitizenInstance.Flags.OnTour) != CitizenInstance.Flags.None}): Setting path mode to 'CalculatingWalkingPathToTarget'"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen ignores cost ({ignoreCost}) or is on a walking tour ({(instanceData.m_flags & CitizenInstance.Flags.OnTour) != CitizenInstance.Flags.None}): Setting path mode to 'CalculatingWalkingPathToTarget'"); #endif - carUsageMode = CarUsagePolicy.Forbidden; - extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget; - } else { - /* - * Citizen is not on a walking tour and is not a mascot - * -> Check if citizen is located at an outside connection and make them obey Parking AI restrictions - */ - - if (instanceData.m_sourceBuilding != 0) { - ItemClass.Service sourceBuildingService = Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].Info.m_class.m_service; - - if (startsAtOutsideConnection) { - if (sourceBuildingService == ItemClass.Service.Road) { - if (vehicleInfo != null) { - /* - * Citizen is located at a road outside connection and can spawn a car - * -> Force car usage - */ + carUsageMode = CarUsagePolicy.Forbidden; + extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget; + } else { + /* + * Citizen is not on a walking tour and is not a mascot + * -> Check if citizen is located at an outside connection and make them obey Parking AI restrictions + */ + + if (instanceData.m_sourceBuilding != 0) { + ItemClass.Service sourceBuildingService = Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].Info.m_class.m_service; + + if (startsAtOutsideConnection) { + if (sourceBuildingService == ItemClass.Service.Road) { + if (vehicleInfo != null) { + /* + * Citizen is located at a road outside connection and can spawn a car + * -> Force car usage + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a road outside connection: Setting path mode to 'RequiresCarPath' and carUsageMode to 'ForcedPocket'"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a road outside connection: Setting path mode to 'RequiresCarPath' and carUsageMode to 'ForcedPocket'"); #endif - extInstance.pathMode = ExtPathMode.RequiresCarPath; - carUsageMode = CarUsagePolicy.ForcedPocket; - } else { - /* - * Citizen is located at a non-road outside connection and cannot spawn a car - * -> Path-finding fails - */ + extInstance.pathMode = ExtPathMode.RequiresCarPath; + carUsageMode = CarUsagePolicy.ForcedPocket; + } else { + /* + * Citizen is located at a non-road outside connection and cannot spawn a car + * -> Path-finding fails + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a road outside connection but does not have a car template: ABORTING PATH-FINDING"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a road outside connection but does not have a car template: ABORTING PATH-FINDING"); #endif - Reset(ref extInstance); - return false; - } - } else { - /* - * Citizen is located at a non-road outside connection - * -> Prohibit car usage - */ + Reset(ref extInstance); + return false; + } + } else { + /* + * Citizen is located at a non-road outside connection + * -> Prohibit car usage + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a non-road outside connection: Setting path mode to 'CalculatingWalkingPathToTarget'"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a non-road outside connection: Setting path mode to 'CalculatingWalkingPathToTarget'"); #endif - extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget; - carUsageMode = CarUsagePolicy.Forbidden; - } - } - } - } - } - - if ((carUsageMode == CarUsagePolicy.Allowed || carUsageMode == CarUsagePolicy.ForcedParked) && parkedVehicleId != 0) { - /* - * Reuse parked vehicle info - */ - vehicleInfo = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].Info; - - /* - * Check if the citizen should return their car back home - */ - if (extInstance.pathMode == ExtPathMode.None && // initiating a new path - homeId != 0 && // home building present - instanceData.m_targetBuilding == homeId // current target is home - ) { - /* - * citizen travels back home - * -> check if their car should be returned - */ - if ((extCitizen.lastTransportMode & ExtTransportMode.Car) != ExtTransportMode.None) { - /* - * citizen travelled by car - * -> return car back home - */ - extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar; - carUsageMode = CarUsagePolicy.Forbidden; + extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget; + carUsageMode = CarUsagePolicy.Forbidden; + } + } + } + } + } + + if ((carUsageMode == CarUsagePolicy.Allowed || carUsageMode == CarUsagePolicy.ForcedParked) && parkedVehicleId != 0) { + /* + * Reuse parked vehicle info + */ + vehicleInfo = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].Info; + + /* + * Check if the citizen should return their car back home + */ + if (extInstance.pathMode == ExtPathMode.None && // initiating a new path + homeId != 0 && // home building present + instanceData.m_targetBuilding == homeId // current target is home + ) { + /* + * citizen travels back home + * -> check if their car should be returned + */ + if ((extCitizen.lastTransportMode & ExtTransportMode.Car) != ExtTransportMode.None) { + /* + * citizen travelled by car + * -> return car back home + */ + extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar; + carUsageMode = CarUsagePolicy.Forbidden; #if DEBUG - if (fineDebug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen used their car before and is not at home. Forcing to walk to parked car."); + if (fineDebug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen used their car before and is not at home. Forcing to walk to parked car."); #endif - } else { - /* - * citizen travelled by other means of transport - * -> check distance between home and parked car. if too far away: force to take the car back home - */ - float distHomeToParked = (Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position - Singleton.instance.m_buildings.m_buffer[homeId].m_position).magnitude; - - if (distHomeToParked > GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome) { - /* - * force to take car back home - */ - extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar; - carUsageMode = CarUsagePolicy.Forbidden; + } else { + /* + * citizen travelled by other means of transport + * -> check distance between home and parked car. if too far away: force to take the car back home + */ + float distHomeToParked = (Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position - Singleton.instance.m_buildings.m_buffer[homeId].m_position).magnitude; + + if (distHomeToParked > GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome) { + /* + * force to take car back home + */ + extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar; + carUsageMode = CarUsagePolicy.Forbidden; #if DEBUG - if (fineDebug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen wants to go home and parked car is too far away ({distHomeToParked}). Forcing walking to parked car."); + if (fineDebug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen wants to go home and parked car is too far away ({distHomeToParked}). Forcing walking to parked car."); #endif - } - } - } - } - - /* - * The following holds: - * - pathMode is now either CalculatingWalkingPathToParkedCar, CalculatingWalkingPathToTarget, RequiresCarPath or None. - * - if pathMode is CalculatingWalkingPathToParkedCar or RequiresCarPath: parked car is present and citizen is not on a walking tour - * - carUsageMode is valid - * - if pathMode is RequiresCarPath: carUsageMode is either ForcedParked or ForcedPocket - */ - - /* - * modify path-finding constraints (vehicleInfo, endPos) if citizen is forced to walk - */ - if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar || extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToTarget) { - /* - * vehicle must not be used since we need a walking path to either - * 1. a parked car or - * 2. the target building - */ - - if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar) { - /* - * walk to parked car - * -> end position is parked car - */ - endPos = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; + } + } + } + } + + /* + * The following holds: + * - pathMode is now either CalculatingWalkingPathToParkedCar, CalculatingWalkingPathToTarget, RequiresCarPath or None. + * - if pathMode is CalculatingWalkingPathToParkedCar or RequiresCarPath: parked car is present and citizen is not on a walking tour + * - carUsageMode is valid + * - if pathMode is RequiresCarPath: carUsageMode is either ForcedParked or ForcedPocket + */ + + /* + * modify path-finding constraints (vehicleInfo, endPos) if citizen is forced to walk + */ + if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar || extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToTarget) { + /* + * vehicle must not be used since we need a walking path to either + * 1. a parked car or + * 2. the target building + */ + + if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar) { + /* + * walk to parked car + * -> end position is parked car + */ + endPos = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; #if DEBUG - if (fineDebug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen shall go to parked vehicle @ {endPos}"); + if (fineDebug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen shall go to parked vehicle @ {endPos}"); #endif - } - } - } + } + } + } #if BENCHMARK } #endif #if DEBUG - if (fineDebug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is allowed to drive their car? {carUsageMode}"); + if (fineDebug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is allowed to drive their car? {carUsageMode}"); #endif - // NON-STOCK CODE END - - /* - * semi-stock code: determine path-finding parameters (laneTypes, vehicleTypes, extVehicleType, etc.) - */ - NetInfo.LaneType laneTypes = NetInfo.LaneType.Pedestrian; - VehicleInfo.VehicleType vehicleTypes = VehicleInfo.VehicleType.None; - bool randomParking = false; - bool combustionEngine = false; - ExtVehicleType extVehicleType = ExtVehicleType.None; - if (vehicleInfo != null) { - if (vehicleInfo.m_class.m_subService == ItemClass.SubService.PublicTransportTaxi) { - if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTaxi) == CitizenInstance.Flags.None && Singleton.instance.m_districts.m_buffer[0].m_productionData.m_finalTaxiCapacity != 0u) { - SimulationManager instance = Singleton.instance; - if (instance.m_isNightTime || instance.m_randomizer.Int32(2u) == 0) { - laneTypes |= (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - vehicleTypes |= vehicleInfo.m_vehicleType; - extVehicleType = ExtVehicleType.Taxi; // NON-STOCK CODE - // NON-STOCK CODE START - if (Options.parkingAI) { - extInstance.pathMode = ExtPathMode.TaxiToTarget; - } - // NON-STOCK CODE END - } - } - } else - // NON-STOCK CODE START - if (vehicleInfo.m_vehicleType == VehicleInfo.VehicleType.Car) { - if (carUsageMode != CarUsagePolicy.Forbidden) { - extVehicleType = ExtVehicleType.PassengerCar; - laneTypes |= NetInfo.LaneType.Vehicle; - vehicleTypes |= vehicleInfo.m_vehicleType; - combustionEngine = vehicleInfo.m_class.m_subService == ItemClass.SubService.ResidentialLow; - } - } else if (vehicleInfo.m_vehicleType == VehicleInfo.VehicleType.Bicycle) { - extVehicleType = ExtVehicleType.Bicycle; - laneTypes |= NetInfo.LaneType.Vehicle; - vehicleTypes |= vehicleInfo.m_vehicleType; - } - // NON-STOCK CODE END - } - - // NON-STOCK CODE START - ExtPathType extPathType = ExtPathType.None; - PathUnit.Position endPosA = default(PathUnit.Position); - bool calculateEndPos = true; - bool allowRandomParking = true; + // NON-STOCK CODE END + + /* + * semi-stock code: determine path-finding parameters (laneTypes, vehicleTypes, extVehicleType, etc.) + */ + NetInfo.LaneType laneTypes = NetInfo.LaneType.Pedestrian; + VehicleInfo.VehicleType vehicleTypes = VehicleInfo.VehicleType.None; + bool randomParking = false; + bool combustionEngine = false; + ExtVehicleType extVehicleType = ExtVehicleType.None; + if (vehicleInfo != null) { + if (vehicleInfo.m_class.m_subService == ItemClass.SubService.PublicTransportTaxi) { + if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTaxi) == CitizenInstance.Flags.None && Singleton.instance.m_districts.m_buffer[0].m_productionData.m_finalTaxiCapacity != 0u) { + SimulationManager instance = Singleton.instance; + if (instance.m_isNightTime || instance.m_randomizer.Int32(2u) == 0) { + laneTypes |= (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + vehicleTypes |= vehicleInfo.m_vehicleType; + extVehicleType = ExtVehicleType.Taxi; // NON-STOCK CODE + // NON-STOCK CODE START + if (Options.parkingAI) { + extInstance.pathMode = ExtPathMode.TaxiToTarget; + } + // NON-STOCK CODE END + } + } + } else + // NON-STOCK CODE START + if (vehicleInfo.m_vehicleType == VehicleInfo.VehicleType.Car) { + if (carUsageMode != CarUsagePolicy.Forbidden) { + extVehicleType = ExtVehicleType.PassengerCar; + laneTypes |= NetInfo.LaneType.Vehicle; + vehicleTypes |= vehicleInfo.m_vehicleType; + combustionEngine = vehicleInfo.m_class.m_subService == ItemClass.SubService.ResidentialLow; + } + } else if (vehicleInfo.m_vehicleType == VehicleInfo.VehicleType.Bicycle) { + extVehicleType = ExtVehicleType.Bicycle; + laneTypes |= NetInfo.LaneType.Vehicle; + vehicleTypes |= vehicleInfo.m_vehicleType; + } + // NON-STOCK CODE END + } + + // NON-STOCK CODE START + ExtPathType extPathType = ExtPathType.None; + PathUnit.Position endPosA = default(PathUnit.Position); + bool calculateEndPos = true; + bool allowRandomParking = true; #if BENCHMARK using (var bm = new Benchmark(null, "ParkingAI.Main")) { #endif - if (Options.parkingAI) { - // Parking AI + if (Options.parkingAI) { + // Parking AI - if (extInstance.pathMode == ExtPathMode.RequiresCarPath) { + if (extInstance.pathMode == ExtPathMode.RequiresCarPath) { #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Setting startPos={startPos} for citizen instance {instanceID}. CurrentDepartureMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Setting startPos={startPos} for citizen instance {instanceID}. CurrentDepartureMode={extInstance.pathMode}"); #endif - if ( - instanceData.m_targetBuilding == 0 || - (Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None - ) { - /* - * the citizen is starting their journey and the target is not an outside connection - * -> find a suitable parking space near the target - */ + if ( + instanceData.m_targetBuilding == 0 || + (Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None + ) { + /* + * the citizen is starting their journey and the target is not an outside connection + * -> find a suitable parking space near the target + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Finding parking space at target for citizen instance {instanceID}. CurrentDepartureMode={extInstance.pathMode} parkedVehicleId={parkedVehicleId}"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Finding parking space at target for citizen instance {instanceID}. CurrentDepartureMode={extInstance.pathMode} parkedVehicleId={parkedVehicleId}"); #endif - // find a parking space in the vicinity of the target - bool calcEndPos; - Vector3 parkPos; - if ( - AdvancedParkingManager.Instance.FindParkingSpaceForCitizen(endPos, vehicleInfo, ref extInstance, homeId, instanceData.m_targetBuilding == homeId, 0, false, out parkPos, ref endPosA, out calcEndPos) && - CalculateReturnPath(ref extInstance, parkPos, endPos) - ) { - // success - extInstance.pathMode = ExtPathMode.CalculatingCarPathToKnownParkPos; - calculateEndPos = calcEndPos; // if true, the end path position still needs to be calculated - allowRandomParking = false; // find a direct path to the calculated parking position + // find a parking space in the vicinity of the target + bool calcEndPos; + Vector3 parkPos; + if ( + AdvancedParkingManager.Instance.FindParkingSpaceForCitizen(endPos, vehicleInfo, ref extInstance, homeId, instanceData.m_targetBuilding == homeId, 0, false, out parkPos, ref endPosA, out calcEndPos) && + CalculateReturnPath(ref extInstance, parkPos, endPos) + ) { + // success + extInstance.pathMode = ExtPathMode.CalculatingCarPathToKnownParkPos; + calculateEndPos = calcEndPos; // if true, the end path position still needs to be calculated + allowRandomParking = false; // find a direct path to the calculated parking position #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Finding known parking space for citizen instance {instanceID}, parked vehicle {parkedVehicleId} succeeded and return path {extInstance.returnPathId} ({extInstance.returnPathState}) is calculating. PathMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Finding known parking space for citizen instance {instanceID}, parked vehicle {parkedVehicleId} succeeded and return path {extInstance.returnPathId} ({extInstance.returnPathState}) is calculating. PathMode={extInstance.pathMode}"); #endif - /*if (! extInstance.CalculateReturnPath(parkPos, endPos)) { - // TODO retry? - if (debug) - Log._Debug($"CustomCitizenAI.CustomStartPathFind: [PFFAIL] Could not calculate return path for citizen instance {instanceID}, parked vehicle {parkedVehicleId}. Calling OnPathFindFailed."); - CustomHumanAI.OnPathFindFailure(extInstance); - return false; - }*/ - } - } - - if (extInstance.pathMode == ExtPathMode.RequiresCarPath) { - /* - * no known parking space found (pathMode has not been updated in the block above) - * -> calculate direct path to target - */ + /*if (! extInstance.CalculateReturnPath(parkPos, endPos)) { + // TODO retry? + if (debug) + Log._Debug($"CustomCitizenAI.CustomStartPathFind: [PFFAIL] Could not calculate return path for citizen instance {instanceID}, parked vehicle {parkedVehicleId}. Calling OnPathFindFailed."); + CustomHumanAI.OnPathFindFailure(extInstance); + return false; + }*/ + } + } + + if (extInstance.pathMode == ExtPathMode.RequiresCarPath) { + /* + * no known parking space found (pathMode has not been updated in the block above) + * -> calculate direct path to target + */ #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen instance {instanceID} is still at CurrentPathMode={extInstance.pathMode} (no parking space found?). Setting it to CalculatingCarPath. parkedVehicleId={parkedVehicleId}"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen instance {instanceID} is still at CurrentPathMode={extInstance.pathMode} (no parking space found?). Setting it to CalculatingCarPath. parkedVehicleId={parkedVehicleId}"); #endif - extInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; - } - } - - /* - * determine path type from path mode - */ - extPathType = extInstance.GetPathType(); - extInstance.atOutsideConnection = startsAtOutsideConnection; - /* - * the following holds: - * - pathMode is now either CalculatingWalkingPathToParkedCar, CalculatingWalkingPathToTarget, CalculatingCarPathToTarget, CalculatingCarPathToKnownParkPos or None. - */ - } + extInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; + } + } + + /* + * determine path type from path mode + */ + extPathType = extInstance.GetPathType(); + extInstance.atOutsideConnection = startsAtOutsideConnection; + /* + * the following holds: + * - pathMode is now either CalculatingWalkingPathToParkedCar, CalculatingWalkingPathToTarget, CalculatingCarPathToTarget, CalculatingCarPathToKnownParkPos or None. + */ + } #if BENCHMARK } #endif - /* - * enable random parking if exact parking space was not calculated yet - */ - if (extVehicleType == ExtVehicleType.PassengerCar || extVehicleType == ExtVehicleType.Bicycle) { - if (allowRandomParking && - instanceData.m_targetBuilding != 0 && - ( - Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].Info.m_class.m_service > ItemClass.Service.Office || - (instanceData.m_flags & CitizenInstance.Flags.TargetIsNode) != 0 - )) { - randomParking = true; - } - } - // NON-STOCK CODE END - - /* - * determine the path position of the parked vehicle - */ - PathUnit.Position parkedVehiclePathPos = default(PathUnit.Position); - if (parkedVehicleId != 0 && extVehicleType == ExtVehicleType.PassengerCar) { - Vector3 position = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; - Constants.ManagerFactory.ExtPathManager.FindPathPositionWithSpiralLoop(position, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkedVehiclePathPos); - } - bool allowUnderground = (instanceData.m_flags & (CitizenInstance.Flags.Underground | CitizenInstance.Flags.Transition)) != CitizenInstance.Flags.None; + /* + * enable random parking if exact parking space was not calculated yet + */ + if (extVehicleType == ExtVehicleType.PassengerCar || extVehicleType == ExtVehicleType.Bicycle) { + if (allowRandomParking && + instanceData.m_targetBuilding != 0 && + ( + Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].Info.m_class.m_service > ItemClass.Service.Office || + (instanceData.m_flags & CitizenInstance.Flags.TargetIsNode) != 0 + )) { + randomParking = true; + } + } + // NON-STOCK CODE END + + /* + * determine the path position of the parked vehicle + */ + PathUnit.Position parkedVehiclePathPos = default(PathUnit.Position); + if (parkedVehicleId != 0 && extVehicleType == ExtVehicleType.PassengerCar) { + Vector3 position = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; + Constants.ManagerFactory.ExtPathManager.FindPathPositionWithSpiralLoop(position, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkedVehiclePathPos); + } + bool allowUnderground = (instanceData.m_flags & (CitizenInstance.Flags.Underground | CitizenInstance.Flags.Transition)) != CitizenInstance.Flags.None; #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Requesting path-finding for citizen instance {instanceID}, citizen {instanceData.m_citizen}, extVehicleType={extVehicleType}, extPathType={extPathType}, startPos={startPos}, endPos={endPos}, sourceBuilding={instanceData.m_sourceBuilding}, targetBuilding={instanceData.m_targetBuilding} pathMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Requesting path-finding for citizen instance {instanceID}, citizen {instanceData.m_citizen}, extVehicleType={extVehicleType}, extPathType={extPathType}, startPos={startPos}, endPos={endPos}, sourceBuilding={instanceData.m_sourceBuilding}, targetBuilding={instanceData.m_targetBuilding} pathMode={extInstance.pathMode}"); #endif - /* - * determine start & end path positions - */ - bool foundEndPos = !calculateEndPos || FindPathPosition(instanceID, ref instanceData, endPos, Options.parkingAI && (instanceData.m_targetBuilding == 0 || (Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None) ? NetInfo.LaneType.Pedestrian : laneTypes, vehicleTypes, false, out endPosA); // NON-STOCK CODE: with Parking AI enabled, the end position must be a pedestrian position - bool foundStartPos = false; - PathUnit.Position startPosA; - - if (Options.parkingAI && (extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget || extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos)) { - /* - * citizen will enter their car now - * -> find a road start position - */ - foundStartPos = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, laneTypes & ~NetInfo.LaneType.Pedestrian, vehicleTypes, allowUnderground, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out startPosA); - } else { - foundStartPos = FindPathPosition(instanceID, ref instanceData, startPos, laneTypes, vehicleTypes, allowUnderground, out startPosA); - } - - /* - * start path-finding - */ - if (foundStartPos && // TODO probably fails if vehicle is parked too far away from road - foundEndPos // NON-STOCK CODE - ) { - - if (enableTransport) { - /* - * public transport usage is allowed for this path - */ - if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { - /* - * citizen may use public transport - */ - laneTypes |= NetInfo.LaneType.PublicTransport; - - uint citizenId = instanceData.m_citizen; - if (citizenId != 0u && (citizenManager.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) { - laneTypes |= NetInfo.LaneType.EvacuationTransport; - } - } else if (Options.parkingAI) { // TODO check for incoming connection - /* - * citizen tried to use public transport but waiting time was too long - * -> add public transport demand for source building - */ - if (instanceData.m_sourceBuilding != 0) { + /* + * determine start & end path positions + */ + bool foundEndPos = !calculateEndPos || FindPathPosition(instanceID, ref instanceData, endPos, Options.parkingAI && (instanceData.m_targetBuilding == 0 || (Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None) ? NetInfo.LaneType.Pedestrian : laneTypes, vehicleTypes, false, out endPosA); // NON-STOCK CODE: with Parking AI enabled, the end position must be a pedestrian position + bool foundStartPos = false; + PathUnit.Position startPosA; + + if (Options.parkingAI && (extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget || extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos)) { + /* + * citizen will enter their car now + * -> find a road start position + */ + foundStartPos = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, laneTypes & ~NetInfo.LaneType.Pedestrian, vehicleTypes, allowUnderground, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out startPosA); + } else { + foundStartPos = FindPathPosition(instanceID, ref instanceData, startPos, laneTypes, vehicleTypes, allowUnderground, out startPosA); + } + + /* + * start path-finding + */ + if (foundStartPos && // TODO probably fails if vehicle is parked too far away from road + foundEndPos // NON-STOCK CODE + ) { + + if (enableTransport) { + /* + * public transport usage is allowed for this path + */ + if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { + /* + * citizen may use public transport + */ + laneTypes |= NetInfo.LaneType.PublicTransport; + + uint citizenId = instanceData.m_citizen; + if (citizenId != 0u && (citizenManager.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) { + laneTypes |= NetInfo.LaneType.EvacuationTransport; + } + } else if (Options.parkingAI) { // TODO check for incoming connection + /* + * citizen tried to use public transport but waiting time was too long + * -> add public transport demand for source building + */ + if (instanceData.m_sourceBuilding != 0) { #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen instance {instanceID} cannot uses public transport from building {instanceData.m_sourceBuilding} to {instanceData.m_targetBuilding}. Incrementing public transport demand."); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen instance {instanceID} cannot uses public transport from building {instanceData.m_sourceBuilding} to {instanceData.m_targetBuilding}. Incrementing public transport demand."); #endif - IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; - extBuildingManager.AddPublicTransportDemand(ref extBuildingManager.ExtBuildings[instanceData.m_sourceBuilding], GlobalConfig.Instance.ParkingAI.PublicTransportDemandWaitingIncrement, true); - } - } - } - - PathUnit.Position dummyPathPos = default(PathUnit.Position); - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = extPathType; - args.extVehicleType = extVehicleType; - args.vehicleId = 0; - args.spawned = (instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = dummyPathPos; - args.endPosA = endPosA; - args.endPosB = dummyPathPos; - args.vehiclePosition = parkedVehiclePathPos; - args.laneTypes = laneTypes; - args.vehicleTypes = vehicleTypes; - args.maxLength = 20000f; - args.isHeavyVehicle = false; - args.hasCombustionEngine = combustionEngine; - args.ignoreBlocked = false; - args.ignoreFlooded = false; - args.ignoreCosts = ignoreCost; - args.randomParking = randomParking; - args.stablePath = false; - args.skipQueue = false; - - if ((instanceData.m_flags & CitizenInstance.Flags.OnTour) != 0) { - args.stablePath = true; - args.maxLength = 160000f; - //args.laneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); - } else { - args.stablePath = false; - args.maxLength = 20000f; - } - - bool res = CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args); - // NON-STOCK CODE END - - if (res) { + IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; + extBuildingManager.AddPublicTransportDemand(ref extBuildingManager.ExtBuildings[instanceData.m_sourceBuilding], GlobalConfig.Instance.ParkingAI.PublicTransportDemandWaitingIncrement, true); + } + } + } + + PathUnit.Position dummyPathPos = default(PathUnit.Position); + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = extPathType; + args.extVehicleType = extVehicleType; + args.vehicleId = 0; + args.spawned = (instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = dummyPathPos; + args.endPosA = endPosA; + args.endPosB = dummyPathPos; + args.vehiclePosition = parkedVehiclePathPos; + args.laneTypes = laneTypes; + args.vehicleTypes = vehicleTypes; + args.maxLength = 20000f; + args.isHeavyVehicle = false; + args.hasCombustionEngine = combustionEngine; + args.ignoreBlocked = false; + args.ignoreFlooded = false; + args.ignoreCosts = ignoreCost; + args.randomParking = randomParking; + args.stablePath = false; + args.skipQueue = false; + + if ((instanceData.m_flags & CitizenInstance.Flags.OnTour) != 0) { + args.stablePath = true; + args.maxLength = 160000f; + //args.laneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); + } else { + args.stablePath = false; + args.maxLength = 20000f; + } + + bool res = CustomPathManager._instance.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args); + // NON-STOCK CODE END + + if (res) { #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Path-finding starts for citizen instance {instanceID}, path={path}, extVehicleType={extVehicleType}, extPathType={extPathType}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, laneType={laneTypes}, vehicleType={vehicleTypes}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, vehiclePos.m_segment={parkedVehiclePathPos.m_segment}, vehiclePos.m_lane={parkedVehiclePathPos.m_lane}, vehiclePos.m_offset={parkedVehiclePathPos.m_offset}"); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Path-finding starts for citizen instance {instanceID}, path={path}, extVehicleType={extVehicleType}, extPathType={extPathType}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, laneType={laneTypes}, vehicleType={vehicleTypes}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, vehiclePos.m_segment={parkedVehiclePathPos.m_segment}, vehiclePos.m_lane={parkedVehiclePathPos.m_lane}, vehiclePos.m_offset={parkedVehiclePathPos.m_offset}"); #endif - if (instanceData.m_path != 0u) { - Singleton.instance.ReleasePath(instanceData.m_path); - } - instanceData.m_path = path; - instanceData.m_flags |= CitizenInstance.Flags.WaitingPath; - return true; - } - } + if (instanceData.m_path != 0u) { + Singleton.instance.ReleasePath(instanceData.m_path); + } + instanceData.m_path = path; + instanceData.m_flags |= CitizenInstance.Flags.WaitingPath; + return true; + } + } #if DEBUG - if (Options.parkingAI) { - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): CustomCitizenAI.CustomStartPathFind: [PFFAIL] failed for citizen instance {instanceID} (CurrentPathMode={extInstance.pathMode}). startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, startPosA.offset={startPosA.m_offset}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, endPosA.offset={endPosA.m_offset}, foundStartPos={foundStartPos}, foundEndPos={foundEndPos}"); - Reset(ref extInstance); - } + if (Options.parkingAI) { + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): CustomCitizenAI.CustomStartPathFind: [PFFAIL] failed for citizen instance {instanceID} (CurrentPathMode={extInstance.pathMode}). startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, startPosA.offset={startPosA.m_offset}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, endPosA.offset={endPosA.m_offset}, foundStartPos={foundStartPos}, foundEndPos={foundEndPos}"); + Reset(ref extInstance); + } #endif - return false; - } - - public bool FindPathPosition(ushort instanceID, ref CitizenInstance instanceData, Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowUnderground, out PathUnit.Position position) { - position = default(PathUnit.Position); - float minDist = 1E+10f; - PathUnit.Position posA; - PathUnit.Position posB; - float distA; - float distB; - if (PathManager.FindPathPosition(pos, ItemClass.Service.Road, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { - minDist = distA; - position = posA; - } - if (PathManager.FindPathPosition(pos, ItemClass.Service.Beautification, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { - minDist = distA; - position = posA; - } - if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None && PathManager.FindPathPosition(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { - minDist = distA; - position = posA; - } - return position.m_segment != 0; - } - - public bool IsValid(ushort instanceId) { - return Constants.ServiceFactory.CitizenService.IsCitizenInstanceValid(instanceId); - } - - public uint GetCitizenId(ushort instanceId) { - uint ret = 0; - Constants.ServiceFactory.CitizenService.ProcessCitizenInstance(instanceId, delegate (ushort citInstId, ref CitizenInstance citizenInst) { - ret = citizenInst.m_citizen; - return true; - }); - return ret; - } - - /// - /// Releases the return path - /// - public void ReleaseReturnPath(ref ExtCitizenInstance extInstance) { + return false; + } + + public bool FindPathPosition(ushort instanceID, ref CitizenInstance instanceData, Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowUnderground, out PathUnit.Position position) { + position = default(PathUnit.Position); + float minDist = 1E+10f; + PathUnit.Position posA; + PathUnit.Position posB; + float distA; + float distB; + if (PathManager.FindPathPosition(pos, ItemClass.Service.Road, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + minDist = distA; + position = posA; + } + if (PathManager.FindPathPosition(pos, ItemClass.Service.Beautification, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + minDist = distA; + position = posA; + } + if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None && PathManager.FindPathPosition(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + minDist = distA; + position = posA; + } + return position.m_segment != 0; + } + + public bool IsValid(ushort instanceId) { + return Constants.ServiceFactory.CitizenService.IsCitizenInstanceValid(instanceId); + } + + public uint GetCitizenId(ushort instanceId) { + uint ret = 0; + Constants.ServiceFactory.CitizenService.ProcessCitizenInstance(instanceId, delegate (ushort citInstId, ref CitizenInstance citizenInst) { + ret = citizenInst.m_citizen; + return true; + }); + return ret; + } + + /// + /// Releases the return path + /// + public void ReleaseReturnPath(ref ExtCitizenInstance extInstance) { #if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif - if (extInstance.returnPathId != 0) { + if (extInstance.returnPathId != 0) { #if DEBUG - if (debug) - Log._Debug($"Releasing return path {extInstance.returnPathId} of citizen instance {extInstance.instanceId}. ReturnPathState={extInstance.returnPathState}"); + if (debug) + Log._Debug($"Releasing return path {extInstance.returnPathId} of citizen instance {extInstance.instanceId}. ReturnPathState={extInstance.returnPathState}"); #endif - Singleton.instance.ReleasePath(extInstance.returnPathId); - extInstance.returnPathId = 0; - } - extInstance.returnPathState = ExtPathState.None; - } + Singleton.instance.ReleasePath(extInstance.returnPathId); + extInstance.returnPathId = 0; + } + extInstance.returnPathState = ExtPathState.None; + } - /// - /// - /// - public void UpdateReturnPathState(ref ExtCitizenInstance extInstance) { + /// + /// + /// + public void UpdateReturnPathState(ref ExtCitizenInstance extInstance) { #if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - if (fineDebug) - Log._Debug($"ExtCitizenInstance.UpdateReturnPathState() called for citizen instance {extInstance.instanceId}"); + if (fineDebug) + Log._Debug($"ExtCitizenInstance.UpdateReturnPathState() called for citizen instance {extInstance.instanceId}"); #endif - if (extInstance.returnPathId != 0 && extInstance.returnPathState == ExtPathState.Calculating) { - byte returnPathFlags = CustomPathManager._instance.m_pathUnits.m_buffer[extInstance.returnPathId].m_pathFindFlags; - if ((returnPathFlags & PathUnit.FLAG_READY) != 0) { - extInstance.returnPathState = ExtPathState.Ready; + if (extInstance.returnPathId != 0 && extInstance.returnPathState == ExtPathState.Calculating) { + byte returnPathFlags = CustomPathManager._instance.m_pathUnits.m_buffer[extInstance.returnPathId].m_pathFindFlags; + if ((returnPathFlags & PathUnit.FLAG_READY) != 0) { + extInstance.returnPathState = ExtPathState.Ready; #if DEBUG - if (fineDebug) - Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} SUCCEEDED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); + if (fineDebug) + Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} SUCCEEDED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); #endif - } else if ((returnPathFlags & PathUnit.FLAG_FAILED) != 0) { - extInstance.returnPathState = ExtPathState.Failed; + } else if ((returnPathFlags & PathUnit.FLAG_FAILED) != 0) { + extInstance.returnPathState = ExtPathState.Failed; #if DEBUG - if (debug) - Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} FAILED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); + if (debug) + Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} FAILED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); #endif - } - } - } + } + } + } - public bool CalculateReturnPath(ref ExtCitizenInstance extInstance, Vector3 parkPos, Vector3 targetPos) { + public bool CalculateReturnPath(ref ExtCitizenInstance extInstance, Vector3 parkPos, Vector3 targetPos) { #if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif - ReleaseReturnPath(ref extInstance); - - PathUnit.Position parkPathPos; - PathUnit.Position targetPathPos = default(PathUnit.Position); - bool foundParkPathPos = CustomPathManager.FindCitizenPathPosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out parkPathPos); - bool foundTargetPathPos = foundParkPathPos && CustomPathManager.FindCitizenPathPosition(targetPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out targetPathPos); - if (foundParkPathPos && foundTargetPathPos) { - - PathUnit.Position dummyPathPos = default(PathUnit.Position); - uint pathId; - PathCreationArgs args; - args.extPathType = ExtPathType.WalkingOnly; - args.extVehicleType = ExtVehicleType.None; - args.vehicleId = 0; - args.spawned = true; - args.buildIndex = Singleton.instance.m_currentBuildIndex; - args.startPosA = parkPathPos; - args.startPosB = dummyPathPos; - args.endPosA = targetPathPos; - args.endPosB = dummyPathPos; - args.vehiclePosition = dummyPathPos; - args.laneTypes = NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport; - args.vehicleTypes = VehicleInfo.VehicleType.None; - args.maxLength = 20000f; - args.isHeavyVehicle = false; - args.hasCombustionEngine = false; - args.ignoreBlocked = false; - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = false; - args.stablePath = false; - args.skipQueue = false; - - if (CustomPathManager._instance.CustomCreatePath(out pathId, ref Singleton.instance.m_randomizer, args)) { + ReleaseReturnPath(ref extInstance); + + PathUnit.Position parkPathPos; + PathUnit.Position targetPathPos = default(PathUnit.Position); + bool foundParkPathPos = CustomPathManager.FindCitizenPathPosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out parkPathPos); + bool foundTargetPathPos = foundParkPathPos && CustomPathManager.FindCitizenPathPosition(targetPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out targetPathPos); + if (foundParkPathPos && foundTargetPathPos) { + + PathUnit.Position dummyPathPos = default(PathUnit.Position); + uint pathId; + PathCreationArgs args; + args.extPathType = ExtPathType.WalkingOnly; + args.extVehicleType = ExtVehicleType.None; + args.vehicleId = 0; + args.spawned = true; + args.buildIndex = Singleton.instance.m_currentBuildIndex; + args.startPosA = parkPathPos; + args.startPosB = dummyPathPos; + args.endPosA = targetPathPos; + args.endPosB = dummyPathPos; + args.vehiclePosition = dummyPathPos; + args.laneTypes = NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport; + args.vehicleTypes = VehicleInfo.VehicleType.None; + args.maxLength = 20000f; + args.isHeavyVehicle = false; + args.hasCombustionEngine = false; + args.ignoreBlocked = false; + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = false; + args.stablePath = false; + args.skipQueue = false; + + if (CustomPathManager._instance.CustomCreatePath(out pathId, ref Singleton.instance.m_randomizer, args)) { #if DEBUG - if (debug) - Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Path-finding starts for return path of citizen instance {extInstance.instanceId}, path={pathId}, parkPathPos.segment={parkPathPos.m_segment}, parkPathPos.lane={parkPathPos.m_lane}, targetPathPos.segment={targetPathPos.m_segment}, targetPathPos.lane={targetPathPos.m_lane}"); + if (debug) + Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Path-finding starts for return path of citizen instance {extInstance.instanceId}, path={pathId}, parkPathPos.segment={parkPathPos.m_segment}, parkPathPos.lane={parkPathPos.m_lane}, targetPathPos.segment={targetPathPos.m_segment}, targetPathPos.lane={targetPathPos.m_lane}"); #endif - extInstance.returnPathId = pathId; - extInstance.returnPathState = ExtPathState.Calculating; - return true; - } - } + extInstance.returnPathId = pathId; + extInstance.returnPathState = ExtPathState.Calculating; + return true; + } + } #if DEBUG - if (debug) - Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Could not find path position(s) for either the parking position or target position of citizen instance {extInstance.instanceId}."); + if (debug) + Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Could not find path position(s) for either the parking position or target position of citizen instance {extInstance.instanceId}."); #endif - return false; - } - - public void Reset(ref ExtCitizenInstance extInstance) { - //Flags = ExtFlags.None; - extInstance.pathMode = ExtPathMode.None; - extInstance.failedParkingAttempts = 0; - extInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; - extInstance.parkingSpaceLocationId = 0; - extInstance.lastDistanceToParkedCar = float.MaxValue; - extInstance.atOutsideConnection = false; - //extInstance.ParkedVehiclePosition = default(Vector3); - ReleaseReturnPath(ref extInstance); - } - - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading {data.Count} extended citizen instances"); - - foreach (Configuration.ExtCitizenInstanceData item in data) { - try { - uint instanceId = item.instanceId; - ExtInstances[instanceId].pathMode = (ExtPathMode)item.pathMode; - ExtInstances[instanceId].failedParkingAttempts = item.failedParkingAttempts; - ExtInstances[instanceId].parkingSpaceLocationId = item.parkingSpaceLocationId; - ExtInstances[instanceId].parkingSpaceLocation = (ExtParkingSpaceLocation)item.parkingSpaceLocation; - if (item.parkingPathStartPositionSegment != 0) { - PathUnit.Position pos = new PathUnit.Position(); - pos.m_segment = item.parkingPathStartPositionSegment; - pos.m_lane = item.parkingPathStartPositionLane; - pos.m_offset = item.parkingPathStartPositionOffset; - ExtInstances[instanceId].parkingPathStartPosition = pos; - } else { - ExtInstances[instanceId].parkingPathStartPosition = null; - } - ExtInstances[instanceId].returnPathId = item.returnPathId; - ExtInstances[instanceId].returnPathState = (ExtPathState)item.returnPathState; - ExtInstances[instanceId].lastDistanceToParkedCar = item.lastDistanceToParkedCar; - } catch (Exception e) { - // ignore, as it's probably corrupt save data. it'll be culled on next save - Log.Warning("Error loading ext. citizen instance: " + e.ToString()); - success = false; - } - } - - return success; - } - - public List SaveData(ref bool success) { - List ret = new List(); - for (uint instanceId = 0; instanceId < CitizenManager.MAX_INSTANCE_COUNT; ++instanceId) { - try { - if ((Singleton.instance.m_instances.m_buffer[instanceId].m_flags & CitizenInstance.Flags.Created) == CitizenInstance.Flags.None) { - continue; - } - - if (ExtInstances[instanceId].pathMode == ExtPathMode.None && ExtInstances[instanceId].returnPathId == 0) { - continue; - } - - Configuration.ExtCitizenInstanceData item = new Configuration.ExtCitizenInstanceData(instanceId); - item.pathMode = (int)ExtInstances[instanceId].pathMode; - item.failedParkingAttempts = ExtInstances[instanceId].failedParkingAttempts; - item.parkingSpaceLocationId = ExtInstances[instanceId].parkingSpaceLocationId; - item.parkingSpaceLocation = (int)ExtInstances[instanceId].parkingSpaceLocation; - if (ExtInstances[instanceId].parkingPathStartPosition != null) { - PathUnit.Position pos = (PathUnit.Position)ExtInstances[instanceId].parkingPathStartPosition; - item.parkingPathStartPositionSegment = pos.m_segment; - item.parkingPathStartPositionLane = pos.m_lane; - item.parkingPathStartPositionOffset = pos.m_offset; - } else { - item.parkingPathStartPositionSegment = 0; - item.parkingPathStartPositionLane = 0; - item.parkingPathStartPositionOffset = 0; - } - item.returnPathId = ExtInstances[instanceId].returnPathId; - item.returnPathState = (int)ExtInstances[instanceId].returnPathState; - item.lastDistanceToParkedCar = ExtInstances[instanceId].lastDistanceToParkedCar; - ret.Add(item); - } catch (Exception ex) { - Log.Error($"Exception occurred while saving ext. citizen instances @ {instanceId}: {ex.ToString()}"); - success = false; - } - } - return ret; - } - - public bool IsAtOutsideConnection(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 startPos) { + return false; + } + + public void Reset(ref ExtCitizenInstance extInstance) { + //Flags = ExtFlags.None; + extInstance.pathMode = ExtPathMode.None; + extInstance.failedParkingAttempts = 0; + extInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; + extInstance.parkingSpaceLocationId = 0; + extInstance.lastDistanceToParkedCar = float.MaxValue; + extInstance.atOutsideConnection = false; + //extInstance.ParkedVehiclePosition = default(Vector3); + ReleaseReturnPath(ref extInstance); + } + + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading {data.Count} extended citizen instances"); + + foreach (Configuration.ExtCitizenInstanceData item in data) { + try { + uint instanceId = item.instanceId; + ExtInstances[instanceId].pathMode = (ExtPathMode)item.pathMode; + ExtInstances[instanceId].failedParkingAttempts = item.failedParkingAttempts; + ExtInstances[instanceId].parkingSpaceLocationId = item.parkingSpaceLocationId; + ExtInstances[instanceId].parkingSpaceLocation = (ExtParkingSpaceLocation)item.parkingSpaceLocation; + if (item.parkingPathStartPositionSegment != 0) { + PathUnit.Position pos = new PathUnit.Position(); + pos.m_segment = item.parkingPathStartPositionSegment; + pos.m_lane = item.parkingPathStartPositionLane; + pos.m_offset = item.parkingPathStartPositionOffset; + ExtInstances[instanceId].parkingPathStartPosition = pos; + } else { + ExtInstances[instanceId].parkingPathStartPosition = null; + } + ExtInstances[instanceId].returnPathId = item.returnPathId; + ExtInstances[instanceId].returnPathState = (ExtPathState)item.returnPathState; + ExtInstances[instanceId].lastDistanceToParkedCar = item.lastDistanceToParkedCar; + } catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Warning("Error loading ext. citizen instance: " + e.ToString()); + success = false; + } + } + + return success; + } + + public List SaveData(ref bool success) { + List ret = new List(); + for (uint instanceId = 0; instanceId < CitizenManager.MAX_INSTANCE_COUNT; ++instanceId) { + try { + if ((Singleton.instance.m_instances.m_buffer[instanceId].m_flags & CitizenInstance.Flags.Created) == CitizenInstance.Flags.None) { + continue; + } + + if (ExtInstances[instanceId].pathMode == ExtPathMode.None && ExtInstances[instanceId].returnPathId == 0) { + continue; + } + + Configuration.ExtCitizenInstanceData item = new Configuration.ExtCitizenInstanceData(instanceId); + item.pathMode = (int)ExtInstances[instanceId].pathMode; + item.failedParkingAttempts = ExtInstances[instanceId].failedParkingAttempts; + item.parkingSpaceLocationId = ExtInstances[instanceId].parkingSpaceLocationId; + item.parkingSpaceLocation = (int)ExtInstances[instanceId].parkingSpaceLocation; + if (ExtInstances[instanceId].parkingPathStartPosition != null) { + PathUnit.Position pos = (PathUnit.Position)ExtInstances[instanceId].parkingPathStartPosition; + item.parkingPathStartPositionSegment = pos.m_segment; + item.parkingPathStartPositionLane = pos.m_lane; + item.parkingPathStartPositionOffset = pos.m_offset; + } else { + item.parkingPathStartPositionSegment = 0; + item.parkingPathStartPositionLane = 0; + item.parkingPathStartPositionOffset = 0; + } + item.returnPathId = ExtInstances[instanceId].returnPathId; + item.returnPathState = (int)ExtInstances[instanceId].returnPathState; + item.lastDistanceToParkedCar = ExtInstances[instanceId].lastDistanceToParkedCar; + ret.Add(item); + } catch (Exception ex) { + Log.Error($"Exception occurred while saving ext. citizen instances @ {instanceId}: {ex.ToString()}"); + success = false; + } + } + return ret; + } + + public bool IsAtOutsideConnection(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 startPos) { #if DEBUG - bool citDebug = - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(instanceId)) && - (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[extInstance.instanceId].m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[extInstance.instanceId].m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log._Debug($"ExtCitizenInstanceManager.IsAtOutsideConnection({extInstance.instanceId}): called. Path: {instanceData.m_path} sourceBuilding={instanceData.m_sourceBuilding}"); + bool citDebug = + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(instanceId)) && + (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[extInstance.instanceId].m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[extInstance.instanceId].m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (debug) + Log._Debug($"ExtCitizenInstanceManager.IsAtOutsideConnection({extInstance.instanceId}): called. Path: {instanceData.m_path} sourceBuilding={instanceData.m_sourceBuilding}"); #endif - bool ret = - (Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None && - (startPos - Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].m_position).magnitude <= GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance; + bool ret = + (Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None && + (startPos - Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].m_position).magnitude <= GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance; #if DEBUG - if (debug) - Log._Debug($"ExtCitizenInstanceManager.IsAtOutsideConnection({instanceId}): ret={ret}"); + if (debug) + Log._Debug($"ExtCitizenInstanceManager.IsAtOutsideConnection({instanceId}): ret={ret}"); #endif - return ret; - } - } -} + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/ExtCitizenManager.cs b/TLM/TLM/Manager/Impl/ExtCitizenManager.cs index 296c5d002..70610e22f 100644 --- a/TLM/TLM/Manager/Impl/ExtCitizenManager.cs +++ b/TLM/TLM/Manager/Impl/ExtCitizenManager.cs @@ -15,6 +15,8 @@ using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + public class ExtCitizenManager : AbstractCustomManager, ICustomDataManager>, IExtCitizenManager { public static ExtCitizenManager Instance = new ExtCitizenManager(); diff --git a/TLM/TLM/Manager/Impl/ExtVehicleManager.cs b/TLM/TLM/Manager/Impl/ExtVehicleManager.cs index 885be306c..12e6e9e3f 100644 --- a/TLM/TLM/Manager/Impl/ExtVehicleManager.cs +++ b/TLM/TLM/Manager/Impl/ExtVehicleManager.cs @@ -1,94 +1,90 @@ -using ColossalFramework; -using ColossalFramework.Math; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using TrafficManager.Custom.AI; -using TrafficManager.State; -using TrafficManager.State.ConfigData; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; - -namespace TrafficManager.Manager.Impl { - public class ExtVehicleManager : AbstractCustomManager, IExtVehicleManager { - public static readonly ExtVehicleManager Instance = new ExtVehicleManager(); - - public const int STATE_UPDATE_SHIFT = 6; - public const int JUNCTION_RECHECK_SHIFT = 4; - public const uint MAX_TIMED_RAND = 100; - public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail; - - /// - /// Known vehicles and their current known positions. Index: vehicle id - /// - public ExtVehicle[] ExtVehicles { get; private set; } = null; - - static ExtVehicleManager() { - Instance = new ExtVehicleManager(); - } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"Ext. vehicles:"); - for (int i = 0; i < ExtVehicles.Length; ++i) { - if ((ExtVehicles[i].flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { - continue; - } - Log._Debug($"Vehicle {i}: {ExtVehicles[i]}"); - } - } - - private ExtVehicleManager() { - ExtVehicles = new ExtVehicle[Constants.ServiceFactory.VehicleService.MaxVehicleCount]; - for (uint i = 0; i < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++i) { - ExtVehicles[i] = new ExtVehicle((ushort)i); - } - } - - public void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState) { - if (transitState != extVehicle.junctionTransitState) { - extVehicle.junctionTransitState = transitState; - extVehicle.lastTransitStateUpdate = Now(); - } - } - - public ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data) { - // (stock code from PassengerCarAI.GetDriverInstance) - CitizenManager citizenManager = Singleton.instance; - uint citizenUnitId = data.m_citizenUnits; - int numIter = 0; - while (citizenUnitId != 0) { - uint nextCitizenUnitId = citizenManager.m_units.m_buffer[citizenUnitId].m_nextUnit; - for (int i = 0; i < 5; i++) { - uint citizenId = citizenManager.m_units.m_buffer[citizenUnitId].GetCitizen(i); - if (citizenId != 0) { - ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[citizenId].m_instance; - if (citizenInstanceId != 0) { - return citizenInstanceId; - } - } - } - citizenUnitId = nextCitizenUnitId; - if (++numIter > CitizenManager.MAX_UNIT_COUNT) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - return 0; - } - - public void LogTraffic(ushort vehicleId, ref Vehicle vehicle) { - LogTraffic(vehicleId, ref vehicle, ref ExtVehicles[vehicleId]); - } - - protected void LogTraffic(ushort vehicleId, ref Vehicle vehicle, ref ExtVehicle extVehicle) { - if (extVehicle.currentSegmentId == 0) { - return; - } +namespace TrafficManager.Manager.Impl { + using System; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using State; + using State.ConfigData; + using Traffic.Data; + using UnityEngine; + + public class ExtVehicleManager : AbstractCustomManager, IExtVehicleManager { + public static readonly ExtVehicleManager Instance = new ExtVehicleManager(); + + public const int STATE_UPDATE_SHIFT = 6; + public const int JUNCTION_RECHECK_SHIFT = 4; + public const uint MAX_TIMED_RAND = 100; + public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail; + + /// + /// Known vehicles and their current known positions. Index: vehicle id + /// + public ExtVehicle[] ExtVehicles { get; private set; } = null; + + static ExtVehicleManager() { + Instance = new ExtVehicleManager(); + } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"Ext. vehicles:"); + for (int i = 0; i < ExtVehicles.Length; ++i) { + if ((ExtVehicles[i].flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + continue; + } + Log._Debug($"Vehicle {i}: {ExtVehicles[i]}"); + } + } + + private ExtVehicleManager() { + ExtVehicles = new ExtVehicle[Constants.ServiceFactory.VehicleService.MaxVehicleCount]; + for (uint i = 0; i < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++i) { + ExtVehicles[i] = new ExtVehicle((ushort)i); + } + } + + public void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState) { + if (transitState != extVehicle.junctionTransitState) { + extVehicle.junctionTransitState = transitState; + extVehicle.lastTransitStateUpdate = Now(); + } + } + + public ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data) { + // (stock code from PassengerCarAI.GetDriverInstance) + CitizenManager citizenManager = Singleton.instance; + uint citizenUnitId = data.m_citizenUnits; + int numIter = 0; + while (citizenUnitId != 0) { + uint nextCitizenUnitId = citizenManager.m_units.m_buffer[citizenUnitId].m_nextUnit; + for (int i = 0; i < 5; i++) { + uint citizenId = citizenManager.m_units.m_buffer[citizenUnitId].GetCitizen(i); + if (citizenId != 0) { + ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[citizenId].m_instance; + if (citizenInstanceId != 0) { + return citizenInstanceId; + } + } + } + citizenUnitId = nextCitizenUnitId; + if (++numIter > CitizenManager.MAX_UNIT_COUNT) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + return 0; + } + + public void LogTraffic(ushort vehicleId, ref Vehicle vehicle) { + LogTraffic(vehicleId, ref vehicle, ref ExtVehicles[vehicleId]); + } + + protected void LogTraffic(ushort vehicleId, ref Vehicle vehicle, ref ExtVehicle extVehicle) { + if (extVehicle.currentSegmentId == 0) { + return; + } #if MEASUREDENSITY ushort length = (ushort)state.totalLength; if (length == 0) { @@ -96,695 +92,698 @@ protected void LogTraffic(ushort vehicleId, ref Vehicle vehicle, ref ExtVehicle } #endif - if (Options.advancedAI) { - TrafficMeasurementManager.Instance.AddTraffic(extVehicle.currentSegmentId, extVehicle.currentLaneIndex + if (Options.advancedAI) { + TrafficMeasurementManager.Instance.AddTraffic(extVehicle.currentSegmentId, extVehicle.currentLaneIndex #if MEASUREDENSITY , length #endif - , (ushort)vehicle.GetLastFrameVelocity().magnitude); - } - } + , (ushort)vehicle.GetLastFrameVelocity().magnitude); + } + } - public void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData) { - OnReleaseVehicle(vehicleId, ref vehicleData); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created || - (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { + public void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData) { + OnReleaseVehicle(vehicleId, ref vehicleData); + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created || + (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreateVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreateVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); #endif - return; - } + return; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreateVehicle({vehicleId}): calling OnCreate for vehicle {vehicleId}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreateVehicle({vehicleId}): calling OnCreate for vehicle {vehicleId}"); #endif - OnCreate(ref ExtVehicles[vehicleId], ref vehicleData); - } + OnCreate(ref ExtVehicles[vehicleId], ref vehicleData); + } - public ExtVehicleType OnStartPathFind(ushort vehicleId, ref Vehicle vehicleData, ExtVehicleType? vehicleType) { - if ((vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None || - (vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { + public ExtVehicleType OnStartPathFind(ushort vehicleId, ref Vehicle vehicleData, ExtVehicleType? vehicleType) { + if ((vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None || + (vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); #endif - return ExtVehicleType.None; - } + return ExtVehicleType.None; + } - ExtVehicleType ret = OnStartPathFind(ref ExtVehicles[vehicleId], ref vehicleData, vehicleType); + ExtVehicleType ret = OnStartPathFind(ref ExtVehicles[vehicleId], ref vehicleData, vehicleType); - ushort connectedVehicleId = vehicleId; - while (true) { - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + ushort connectedVehicleId = vehicleId; + while (true) { + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - if (connectedVehicleId == 0) { - break; - } + if (connectedVehicleId == 0) { + break; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): overriding vehicle type for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): overriding vehicle type for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); #endif - OnStartPathFind(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], vehicleType); - } + OnStartPathFind(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], vehicleType); + } - return ret; - } + return ret; + } - public void OnSpawnVehicle(ushort vehicleId, ref Vehicle vehicleData) { - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Spawned)) != (Vehicle.Flags.Created | Vehicle.Flags.Spawned) || - (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { + public void OnSpawnVehicle(ushort vehicleId, ref Vehicle vehicleData) { + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Spawned)) != (Vehicle.Flags.Created | Vehicle.Flags.Spawned) || + (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}, path: {vehicleData.m_path}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}, path: {vehicleData.m_path}"); #endif - return; - } + return; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): calling OnSpawn for vehicle {vehicleId}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): calling OnSpawn for vehicle {vehicleId}"); #endif - ushort connectedVehicleId = vehicleId; - while (connectedVehicleId != 0) { - OnSpawn(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId]); - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - } - } + ushort connectedVehicleId = vehicleId; + while (connectedVehicleId != 0) { + OnSpawn(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId]); + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + } + } - public void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData) { - ushort connectedVehicleId = vehicleId; - while (connectedVehicleId != 0) { - UpdateVehiclePosition(ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], ref ExtVehicles[connectedVehicleId]); - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - } - } + public void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData) { + ushort connectedVehicleId = vehicleId; + while (connectedVehicleId != 0) { + UpdateVehiclePosition(ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], ref ExtVehicles[connectedVehicleId]); + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + } + } - protected void UpdateVehiclePosition(ref Vehicle vehicleData, ref ExtVehicle extVehicle) { + protected void UpdateVehiclePosition(ref Vehicle vehicleData, ref ExtVehicle extVehicle) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdateVehiclePosition({extVehicle.vehicleId}) called"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdateVehiclePosition({extVehicle.vehicleId}) called"); #endif - if (vehicleData.m_path == 0 || (vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0 || - (extVehicle.lastPathId == vehicleData.m_path && extVehicle.lastPathPositionIndex == vehicleData.m_pathPositionIndex) - ) { - return; - } + if (vehicleData.m_path == 0 || (vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0 || + (extVehicle.lastPathId == vehicleData.m_path && extVehicle.lastPathPositionIndex == vehicleData.m_pathPositionIndex) + ) { + return; + } - PathManager pathManager = Singleton.instance; - IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + PathManager pathManager = Singleton.instance; + IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - // update vehicle position for timed traffic lights and priority signs - int coarsePathPosIndex = vehicleData.m_pathPositionIndex >> 1; - PathUnit.Position curPathPos = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(coarsePathPosIndex); - PathUnit.Position nextPathPos = default(PathUnit.Position); - pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(coarsePathPosIndex, out nextPathPos); - bool startNode = IsTransitNodeCurStartNode(ref curPathPos, ref nextPathPos); - UpdatePosition(ref extVehicle, ref vehicleData, ref segmentEndMan.ExtSegmentEnds[segmentEndMan.GetIndex(curPathPos.m_segment, startNode)], ref curPathPos, ref nextPathPos); - } + // update vehicle position for timed traffic lights and priority signs + int coarsePathPosIndex = vehicleData.m_pathPositionIndex >> 1; + PathUnit.Position curPathPos = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(coarsePathPosIndex); + PathUnit.Position nextPathPos = default(PathUnit.Position); + pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(coarsePathPosIndex, out nextPathPos); + bool startNode = IsTransitNodeCurStartNode(ref curPathPos, ref nextPathPos); + UpdatePosition(ref extVehicle, ref vehicleData, ref segmentEndMan.ExtSegmentEnds[segmentEndMan.GetIndex(curPathPos.m_segment, startNode)], ref curPathPos, ref nextPathPos); + } - public void OnDespawnVehicle(ushort vehicleId, ref Vehicle vehicleData) { - if ((vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None || - (vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Spawned)) == 0) { + public void OnDespawnVehicle(ushort vehicleId, ref Vehicle vehicleData) { + if ((vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None || + (vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Spawned)) == 0) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); #endif - return; - } + return; + } - ushort connectedVehicleId = vehicleId; - while (connectedVehicleId != 0) { + ushort connectedVehicleId = vehicleId; + while (connectedVehicleId != 0) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): calling OnDespawn for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): calling OnDespawn for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); #endif - OnDespawn(ref ExtVehicles[connectedVehicleId]); - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - } - } + OnDespawn(ref ExtVehicles[connectedVehicleId]); + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + } + } - public void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData) { + public void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}) called."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}) called."); #endif - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created || - (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { + if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created || + (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); #endif - return; - } + return; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): calling OnRelease for vehicle {vehicleId}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): calling OnRelease for vehicle {vehicleId}"); #endif - OnRelease(ref ExtVehicles[vehicleId], ref vehicleData); - } + OnRelease(ref ExtVehicles[vehicleId], ref vehicleData); + } - public void Unlink(ref ExtVehicle extVehicle) { + public void Unlink(ref ExtVehicle extVehicle) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) called: Unlinking vehicle from all segment ends\nstate:{extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) called: Unlinking vehicle from all segment ends\nstate:{extVehicle}"); - ushort prevSegmentId = extVehicle.currentSegmentId; - bool prevStartNode = extVehicle.currentStartNode; + ushort prevSegmentId = extVehicle.currentSegmentId; + bool prevStartNode = extVehicle.currentStartNode; #endif - extVehicle.lastPositionUpdate = Now(); + extVehicle.lastPositionUpdate = Now(); - if (extVehicle.previousVehicleIdOnSegment != 0) { - ExtVehicles[extVehicle.previousVehicleIdOnSegment].nextVehicleIdOnSegment = extVehicle.nextVehicleIdOnSegment; - } else if (extVehicle.currentSegmentId != 0) { - IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - int endIndex = segmentEndMan.GetIndex(extVehicle.currentSegmentId, extVehicle.currentStartNode); - if (segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId == extVehicle.vehicleId) { - segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId = extVehicle.nextVehicleIdOnSegment; - } else { - Log.Error($"ExtVehicleManager.Unlink({extVehicle.vehicleId}): Unexpected first vehicle on segment {extVehicle.currentSegmentId}: {segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId}"); - } - } + if (extVehicle.previousVehicleIdOnSegment != 0) { + ExtVehicles[extVehicle.previousVehicleIdOnSegment].nextVehicleIdOnSegment = extVehicle.nextVehicleIdOnSegment; + } else if (extVehicle.currentSegmentId != 0) { + IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + int endIndex = segmentEndMan.GetIndex(extVehicle.currentSegmentId, extVehicle.currentStartNode); + if (segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId == extVehicle.vehicleId) { + segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId = extVehicle.nextVehicleIdOnSegment; + } else { + Log.Error($"ExtVehicleManager.Unlink({extVehicle.vehicleId}): Unexpected first vehicle on segment {extVehicle.currentSegmentId}: {segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId}"); + } + } - if (extVehicle.nextVehicleIdOnSegment != 0) { - ExtVehicles[extVehicle.nextVehicleIdOnSegment].previousVehicleIdOnSegment = extVehicle.previousVehicleIdOnSegment; - } + if (extVehicle.nextVehicleIdOnSegment != 0) { + ExtVehicles[extVehicle.nextVehicleIdOnSegment].previousVehicleIdOnSegment = extVehicle.previousVehicleIdOnSegment; + } - extVehicle.nextVehicleIdOnSegment = 0; - extVehicle.previousVehicleIdOnSegment = 0; + extVehicle.nextVehicleIdOnSegment = 0; + extVehicle.previousVehicleIdOnSegment = 0; - extVehicle.currentSegmentId = 0; - extVehicle.currentStartNode = false; - extVehicle.currentLaneIndex = 0; + extVehicle.currentSegmentId = 0; + extVehicle.currentStartNode = false; + extVehicle.currentLaneIndex = 0; - extVehicle.lastPathId = 0; - extVehicle.lastPathPositionIndex = 0; + extVehicle.lastPathId = 0; + extVehicle.lastPathPositionIndex = 0; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[12]) { - IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) finished: Unlinked vehicle from all segment ends\nstate:{extVehicle}\nold segment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(prevSegmentId, prevStartNode)}"); - } + if (GlobalConfig.Instance.Debug.Switches[12]) { + IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) finished: Unlinked vehicle from all segment ends\nstate:{extVehicle}\nold segment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(prevSegmentId, prevStartNode)}"); + } #endif - } + } - /// - /// Links the given vehicle to the given segment end. - /// - /// vehicle - /// ext. segment end - /// lane index - private void Link(ref ExtVehicle extVehicle, ref ExtSegmentEnd end, byte laneIndex) { + /// + /// Links the given vehicle to the given segment end. + /// + /// vehicle + /// ext. segment end + /// lane index + private void Link(ref ExtVehicle extVehicle, ref ExtSegmentEnd end, byte laneIndex) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) called: Linking vehicle to segment end {end}\nstate:{extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) called: Linking vehicle to segment end {end}\nstate:{extVehicle}"); #endif - extVehicle.currentSegmentId = end.segmentId; - extVehicle.currentStartNode = end.startNode; - extVehicle.currentLaneIndex = laneIndex; + extVehicle.currentSegmentId = end.segmentId; + extVehicle.currentStartNode = end.startNode; + extVehicle.currentLaneIndex = laneIndex; - ushort oldFirstRegVehicleId = end.firstVehicleId; - if (oldFirstRegVehicleId != 0) { - ExtVehicles[oldFirstRegVehicleId].previousVehicleIdOnSegment = extVehicle.vehicleId; - extVehicle.nextVehicleIdOnSegment = oldFirstRegVehicleId; - } - end.firstVehicleId = extVehicle.vehicleId; + ushort oldFirstRegVehicleId = end.firstVehicleId; + if (oldFirstRegVehicleId != 0) { + ExtVehicles[oldFirstRegVehicleId].previousVehicleIdOnSegment = extVehicle.vehicleId; + extVehicle.nextVehicleIdOnSegment = oldFirstRegVehicleId; + } + end.firstVehicleId = extVehicle.vehicleId; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[12]) - Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) finished: Linked vehicle to segment end: {end}\nstate:{extVehicle}\nsegment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(extVehicle.currentSegmentId, extVehicle.currentStartNode)}"); + if (GlobalConfig.Instance.Debug.Switches[12]) + Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) finished: Linked vehicle to segment end: {end}\nstate:{extVehicle}\nsegment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(extVehicle.currentSegmentId, extVehicle.currentStartNode)}"); #endif - } + } - public void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { + public void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Created) != ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) != ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}): Vehicle is already created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}): Vehicle is already created."); #endif - OnRelease(ref extVehicle, ref vehicleData); - } + OnRelease(ref extVehicle, ref vehicleData); + } - DetermineVehicleType(ref extVehicle, ref vehicleData); - extVehicle.recklessDriver = false; - extVehicle.flags = ExtVehicleFlags.Created; + DetermineVehicleType(ref extVehicle, ref vehicleData); + extVehicle.recklessDriver = false; + extVehicle.flags = ExtVehicleFlags.Created; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) finished: {extVehicle}"); #endif - } + } - public ExtVehicleType OnStartPathFind(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ExtVehicleType? vehicleType) { + public API.Traffic.Enums.ExtVehicleType OnStartPathFind( + ref ExtVehicle extVehicle, + ref Vehicle vehicleData, + API.Traffic.Enums.ExtVehicleType? vehicleType) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}): Vehicle has not yet been created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}): Vehicle has not yet been created."); #endif - OnCreate(ref extVehicle, ref vehicleData); - } + OnCreate(ref extVehicle, ref vehicleData); + } - if (vehicleType != null) { - extVehicle.vehicleType = (ExtVehicleType)vehicleType; - } + if (vehicleType != null) { + extVehicle.vehicleType = (API.Traffic.Enums.ExtVehicleType)vehicleType; + } - extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); + extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) finished: {extVehicle}"); #endif - StepRand(ref extVehicle, true); - UpdateDynamicLaneSelectionParameters(ref extVehicle); + StepRand(ref extVehicle, true); + UpdateDynamicLaneSelectionParameters(ref extVehicle); - return extVehicle.vehicleType; - } + return extVehicle.vehicleType; + } - public void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { + public void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Vehicle has not yet been created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Vehicle has not yet been created."); #endif - OnCreate(ref extVehicle, ref vehicleData); - } + OnCreate(ref extVehicle, ref vehicleData); + } - Unlink(ref extVehicle); + Unlink(ref extVehicle); - extVehicle.lastPathId = 0; - extVehicle.lastPathPositionIndex = 0; - extVehicle.lastAltLaneSelSegmentId = 0; - extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); - StepRand(ref extVehicle, true); - UpdateDynamicLaneSelectionParameters(ref extVehicle); + extVehicle.lastPathId = 0; + extVehicle.lastPathPositionIndex = 0; + extVehicle.lastAltLaneSelSegmentId = 0; + extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); + StepRand(ref extVehicle, true); + UpdateDynamicLaneSelectionParameters(ref extVehicle); - try { - extVehicle.totalLength = vehicleData.CalculateTotalLength(extVehicle.vehicleId); - } catch (Exception + try { + extVehicle.totalLength = vehicleData.CalculateTotalLength(extVehicle.vehicleId); + } catch (Exception #if DEBUG - e + e #endif - ) { - extVehicle.totalLength = 0; + ) { + extVehicle.totalLength = 0; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Error occurred while calculating total length: {e}\nstate: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Error occurred while calculating total length: {e}\nstate: {extVehicle}"); #endif - return; - } + return; + } - extVehicle.flags |= ExtVehicleFlags.Spawned; + extVehicle.flags |= ExtVehicleFlags.Spawned; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) finished: {extVehicle}"); #endif - } + } - public void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { + public void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Vehicle is not yet spawned."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Vehicle is not yet spawned."); #endif - OnSpawn(ref extVehicle, ref vehicleData); - } + OnSpawn(ref extVehicle, ref vehicleData); + } - if (extVehicle.nextSegmentId != nextPos.m_segment || extVehicle.nextLaneIndex != nextPos.m_lane) { - extVehicle.nextSegmentId = nextPos.m_segment; - extVehicle.nextLaneIndex = nextPos.m_lane; - } + if (extVehicle.nextSegmentId != nextPos.m_segment || extVehicle.nextLaneIndex != nextPos.m_lane) { + extVehicle.nextSegmentId = nextPos.m_segment; + extVehicle.nextLaneIndex = nextPos.m_lane; + } - bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); - //ISegmentEnd end = Constants.ManagerFactory.SegmentEndManager.GetSegmentEnd(curPos.m_segment, startNode); + bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); + //ISegmentEnd end = Constants.ManagerFactory.SegmentEndManager.GetSegmentEnd(curPos.m_segment, startNode); - if (extVehicle.currentSegmentId != segEnd.segmentId || extVehicle.currentStartNode != segEnd.startNode || extVehicle.currentLaneIndex != curPos.m_lane) { + if (extVehicle.currentSegmentId != segEnd.segmentId || extVehicle.currentStartNode != segEnd.startNode || extVehicle.currentLaneIndex != curPos.m_lane) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Current segment end changed. seg. {extVehicle.currentSegmentId}, start {extVehicle.currentStartNode}, lane {extVehicle.currentLaneIndex} -> seg. {segEnd.segmentId}, start {segEnd.startNode}, lane {curPos.m_lane}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Current segment end changed. seg. {extVehicle.currentSegmentId}, start {extVehicle.currentStartNode}, lane {extVehicle.currentLaneIndex} -> seg. {segEnd.segmentId}, start {segEnd.startNode}, lane {curPos.m_lane}"); #endif - if (extVehicle.currentSegmentId != 0) { + if (extVehicle.currentSegmentId != 0) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Unlinking from current segment end"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Unlinking from current segment end"); #endif - Unlink(ref extVehicle); - } + Unlink(ref extVehicle); + } - extVehicle.lastPathId = vehicleData.m_path; - extVehicle.lastPathPositionIndex = vehicleData.m_pathPositionIndex; + extVehicle.lastPathId = vehicleData.m_path; + extVehicle.lastPathPositionIndex = vehicleData.m_pathPositionIndex; - extVehicle.waitTime = 0; + extVehicle.waitTime = 0; #if DEBUGVSTATE - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Linking vehicle to segment end {segEnd.segmentId} @ {segEnd.startNode} ({segEnd.nodeId}). Current position: Seg. {curPos.m_segment}, lane {curPos.m_lane}, offset {curPos.m_offset} / Next position: Seg. {nextPos.m_segment}, lane {nextPos.m_lane}, offset {nextPos.m_offset}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Linking vehicle to segment end {segEnd.segmentId} @ {segEnd.startNode} ({segEnd.nodeId}). Current position: Seg. {curPos.m_segment}, lane {curPos.m_lane}, offset {curPos.m_offset} / Next position: Seg. {nextPos.m_segment}, lane {nextPos.m_lane}, offset {nextPos.m_offset}"); #endif - if (segEnd.segmentId != 0) { - Link(ref extVehicle, ref segEnd, curPos.m_lane); - } - SetJunctionTransitState(ref extVehicle, VehicleJunctionTransitState.Approach); - } + if (segEnd.segmentId != 0) { + Link(ref extVehicle, ref segEnd, curPos.m_lane); + } + SetJunctionTransitState(ref extVehicle, VehicleJunctionTransitState.Approach); + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) finshed: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) finshed: {extVehicle}"); #endif - } + } - public void OnDespawn(ref ExtVehicle extVehicle) { + public void OnDespawn(ref ExtVehicle extVehicle) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId} called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId} called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}): Vehicle is not spawned."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}): Vehicle is not spawned."); #endif - return; - } - - Constants.ManagerFactory.ExtCitizenInstanceManager.ResetInstance(GetDriverInstanceId(extVehicle.vehicleId, ref Singleton.instance.m_vehicles.m_buffer[extVehicle.vehicleId])); + return; + } - Unlink(ref extVehicle); + Constants.ManagerFactory.ExtCitizenInstanceManager.ResetInstance(GetDriverInstanceId(extVehicle.vehicleId, ref Singleton.instance.m_vehicles.m_buffer[extVehicle.vehicleId])); - extVehicle.lastAltLaneSelSegmentId = 0; - extVehicle.recklessDriver = false; - extVehicle.nextSegmentId = 0; - extVehicle.nextLaneIndex = 0; - extVehicle.totalLength = 0; - extVehicle.flags &= ExtVehicleFlags.Created; + Unlink(ref extVehicle); -#if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}) finished: {extVehicle}"); -#endif - } + extVehicle.lastAltLaneSelSegmentId = 0; + extVehicle.recklessDriver = false; + extVehicle.nextSegmentId = 0; + extVehicle.nextLaneIndex = 0; + extVehicle.totalLength = 0; + extVehicle.flags &= ExtVehicleFlags.Created; - public void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}) finished: {extVehicle}"); #endif + } - if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { + public void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is not created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) called: {extVehicle}"); #endif - return; - } - if ((extVehicle.flags & ExtVehicleFlags.Spawned) != ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is spawned."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is not created."); #endif - OnDespawn(ref extVehicle); - } else { - Unlink(ref extVehicle); - } - - extVehicle.lastTransitStateUpdate = 0; - extVehicle.lastPositionUpdate = 0; - extVehicle.waitTime = 0; - extVehicle.flags = ExtVehicleFlags.None; - extVehicle.vehicleType = ExtVehicleType.None; - extVehicle.heavyVehicle = false; - extVehicle.lastAltLaneSelSegmentId = 0; - extVehicle.junctionTransitState = VehicleJunctionTransitState.None; - extVehicle.recklessDriver = false; + return; + } + if ((extVehicle.flags & ExtVehicleFlags.Spawned) != ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is spawned."); #endif - } - - public bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle) { - uint frame = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; - return (extVehicle.lastTransitStateUpdate >> STATE_UPDATE_SHIFT) >= (frame >> STATE_UPDATE_SHIFT); - } - - public uint GetStaticVehicleRand(ushort vehicleId) { - return vehicleId % 100u; - } - - public uint GetTimedVehicleRand(ushort vehicleId) { - return (uint)((vehicleId % 2) * 50u + (ExtVehicles[vehicleId].timedRand >> 1)); - } - - public void StepRand(ref ExtVehicle extVehicle, bool force) { - Randomizer rand = Constants.ServiceFactory.SimulationService.Randomizer; - if (force || (rand.UInt32(GlobalConfig.Instance.Gameplay.VehicleTimedRandModulo) == 0)) { - extVehicle.timedRand = Options.individualDrivingStyle ? (byte)rand.UInt32(100) : (byte)50; - } - } - - public void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle) { -#if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"VehicleState.UpdateDynamicLaneSelectionParameters({extVehicle.vehicleId}) called."); -#endif - - if (!Options.IsDynamicLaneSelectionActive()) { - extVehicle.dlsReady = false; - return; - } - - if (extVehicle.dlsReady) { - return; - } - - float egoism = (float)extVehicle.timedRand / 100f; - float altruism = 1f - egoism; - DynamicLaneSelection dls = GlobalConfig.Instance.DynamicLaneSelection; - - if (Options.individualDrivingStyle) { - extVehicle.maxReservedSpace = extVehicle.recklessDriver - ? Mathf.Lerp(dls.MinMaxRecklessReservedSpace, dls.MaxMaxRecklessReservedSpace, altruism) - : Mathf.Lerp(dls.MinMaxReservedSpace, dls.MaxMaxReservedSpace, altruism); - extVehicle.laneSpeedRandInterval = Mathf.Lerp(dls.MinLaneSpeedRandInterval, dls.MaxLaneSpeedRandInterval, egoism); - extVehicle.maxOptLaneChanges = (int)Math.Round(Mathf.Lerp(dls.MinMaxOptLaneChanges, dls.MaxMaxOptLaneChanges + 1, egoism)); - extVehicle.maxUnsafeSpeedDiff = Mathf.Lerp(dls.MinMaxUnsafeSpeedDiff, dls.MaxMaxOptLaneChanges, egoism); - extVehicle.minSafeSpeedImprovement = Mathf.Lerp(dls.MinMinSafeSpeedImprovement, dls.MaxMinSafeSpeedImprovement, altruism); - extVehicle.minSafeTrafficImprovement = Mathf.Lerp(dls.MinMinSafeTrafficImprovement, dls.MaxMinSafeTrafficImprovement, altruism); - } else { - extVehicle.maxReservedSpace = extVehicle.recklessDriver ? dls.MaxRecklessReservedSpace : dls.MaxReservedSpace; - extVehicle.laneSpeedRandInterval = dls.LaneSpeedRandInterval; - extVehicle.maxOptLaneChanges = dls.MaxOptLaneChanges; - extVehicle.maxUnsafeSpeedDiff = dls.MaxUnsafeSpeedDiff; - extVehicle.minSafeSpeedImprovement = dls.MinSafeSpeedImprovement; - extVehicle.minSafeTrafficImprovement = dls.MinSafeTrafficImprovement; - } - extVehicle.dlsReady = true; - } - - private static ushort GetTransitNodeId(ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { - bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); - ushort transitNodeId1 = 0; - Constants.ServiceFactory.NetService.ProcessSegment(curPos.m_segment, delegate (ushort segmentId, ref NetSegment segment) { - transitNodeId1 = startNode ? segment.m_startNode : segment.m_endNode; - return true; - }); - - ushort transitNodeId2 = 0; - Constants.ServiceFactory.NetService.ProcessSegment(nextPos.m_segment, delegate (ushort segmentId, ref NetSegment segment) { - transitNodeId2 = startNode ? segment.m_startNode : segment.m_endNode; - return true; - }); - - if (transitNodeId1 != transitNodeId2) { - return 0; - } - return transitNodeId1; - } - - private static bool IsTransitNodeCurStartNode(ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { - // note: does not check if curPos and nextPos are successive path positions - bool startNode; - if (curPos.m_offset == 0) { - startNode = true; - } else if (curPos.m_offset == 255) { - startNode = false; - } else if (nextPos.m_offset == 0) { - startNode = true; - } else { - startNode = false; - } - return startNode; - } - - private static uint Now() { - return Constants.ServiceFactory.SimulationService.CurrentFrameIndex; - } - - private void DetermineVehicleType(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { - VehicleAI ai = vehicleData.Info.m_vehicleAI; - - if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { - extVehicle.vehicleType = ExtVehicleType.Emergency; - } else { - ExtVehicleType? type = DetermineVehicleTypeFromAIType(extVehicle.vehicleId, ai, false); - if (type != null) { - extVehicle.vehicleType = (ExtVehicleType)type; - } else { - extVehicle.vehicleType = ExtVehicleType.None; - } - } - - if (extVehicle.vehicleType == ExtVehicleType.CargoTruck) { - extVehicle.heavyVehicle = ((CargoTruckAI)ai).m_isHeavyVehicle; - } else { - extVehicle.heavyVehicle = false; - } + OnDespawn(ref extVehicle); + } else { + Unlink(ref extVehicle); + } -#if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.DetermineVehicleType({extVehicle.vehicleId}): vehicleType={extVehicle.vehicleType}, heavyVehicle={extVehicle.heavyVehicle}. Info={vehicleData.Info?.name}"); -#endif - } - - private ExtVehicleType? DetermineVehicleTypeFromAIType(ushort vehicleId, VehicleAI ai, bool emergencyOnDuty) { - if (emergencyOnDuty) - return ExtVehicleType.Emergency; - - switch (ai.m_info.m_vehicleType) { - case VehicleInfo.VehicleType.Bicycle: - return ExtVehicleType.Bicycle; - case VehicleInfo.VehicleType.Car: - if (ai is PassengerCarAI) - return ExtVehicleType.PassengerCar; - if (ai is AmbulanceAI || ai is FireTruckAI || ai is PoliceCarAI || ai is HearseAI || ai is GarbageTruckAI || ai is MaintenanceTruckAI || ai is SnowTruckAI || ai is WaterTruckAI || ai is DisasterResponseVehicleAI || ai is ParkMaintenanceVehicleAI || ai is PostVanAI) { - return ExtVehicleType.Service; - } - if (ai is CarTrailerAI) - return ExtVehicleType.None; - if (ai is BusAI) - return ExtVehicleType.Bus; - if (ai is TaxiAI) - return ExtVehicleType.Taxi; - if (ai is CargoTruckAI) - return ExtVehicleType.CargoTruck; - break; - case VehicleInfo.VehicleType.Metro: - case VehicleInfo.VehicleType.Train: - case VehicleInfo.VehicleType.Monorail: - if (ai is CargoTrainAI) - return ExtVehicleType.CargoTrain; - return ExtVehicleType.PassengerTrain; - case VehicleInfo.VehicleType.Tram: - return ExtVehicleType.Tram; - case VehicleInfo.VehicleType.Ship: - if (ai is PassengerShipAI) - return ExtVehicleType.PassengerShip; - //if (ai is CargoShipAI) - return ExtVehicleType.CargoShip; - //break; - case VehicleInfo.VehicleType.Plane: - if (ai is PassengerPlaneAI) - return ExtVehicleType.PassengerPlane; - if (ai is CargoPlaneAI) - return ExtVehicleType.CargoPlane; - break; - case VehicleInfo.VehicleType.Helicopter: - //if (ai is PassengerPlaneAI) - return ExtVehicleType.Helicopter; - //break; - case VehicleInfo.VehicleType.Ferry: - return ExtVehicleType.Ferry; - case VehicleInfo.VehicleType.Blimp: - return ExtVehicleType.Blimp; - case VehicleInfo.VehicleType.CableCar: - return ExtVehicleType.CableCar; - } + extVehicle.lastTransitStateUpdate = 0; + extVehicle.lastPositionUpdate = 0; + extVehicle.waitTime = 0; + extVehicle.flags = ExtVehicleFlags.None; + extVehicle.vehicleType = API.Traffic.Enums.ExtVehicleType.None; + extVehicle.heavyVehicle = false; + extVehicle.lastAltLaneSelSegmentId = 0; + extVehicle.junctionTransitState = VehicleJunctionTransitState.None; + extVehicle.recklessDriver = false; + +#if DEBUG + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) finished: {extVehicle}"); +#endif + } + + public bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle) { + uint frame = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; + return (extVehicle.lastTransitStateUpdate >> STATE_UPDATE_SHIFT) >= (frame >> STATE_UPDATE_SHIFT); + } + + public uint GetStaticVehicleRand(ushort vehicleId) { + return vehicleId % 100u; + } + + public uint GetTimedVehicleRand(ushort vehicleId) { + return (uint)((vehicleId % 2) * 50u + (ExtVehicles[vehicleId].timedRand >> 1)); + } + + public void StepRand(ref ExtVehicle extVehicle, bool force) { + Randomizer rand = Constants.ServiceFactory.SimulationService.Randomizer; + if (force || (rand.UInt32(GlobalConfig.Instance.Gameplay.VehicleTimedRandModulo) == 0)) { + extVehicle.timedRand = Options.individualDrivingStyle ? (byte)rand.UInt32(100) : (byte)50; + } + } + + public void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle) { +#if DEBUG + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"VehicleState.UpdateDynamicLaneSelectionParameters({extVehicle.vehicleId}) called."); +#endif + + if (!Options.IsDynamicLaneSelectionActive()) { + extVehicle.dlsReady = false; + return; + } + + if (extVehicle.dlsReady) { + return; + } + + float egoism = (float)extVehicle.timedRand / 100f; + float altruism = 1f - egoism; + DynamicLaneSelection dls = GlobalConfig.Instance.DynamicLaneSelection; + + if (Options.individualDrivingStyle) { + extVehicle.maxReservedSpace = extVehicle.recklessDriver + ? Mathf.Lerp(dls.MinMaxRecklessReservedSpace, dls.MaxMaxRecklessReservedSpace, altruism) + : Mathf.Lerp(dls.MinMaxReservedSpace, dls.MaxMaxReservedSpace, altruism); + extVehicle.laneSpeedRandInterval = Mathf.Lerp(dls.MinLaneSpeedRandInterval, dls.MaxLaneSpeedRandInterval, egoism); + extVehicle.maxOptLaneChanges = (int)Math.Round(Mathf.Lerp(dls.MinMaxOptLaneChanges, dls.MaxMaxOptLaneChanges + 1, egoism)); + extVehicle.maxUnsafeSpeedDiff = Mathf.Lerp(dls.MinMaxUnsafeSpeedDiff, dls.MaxMaxOptLaneChanges, egoism); + extVehicle.minSafeSpeedImprovement = Mathf.Lerp(dls.MinMinSafeSpeedImprovement, dls.MaxMinSafeSpeedImprovement, altruism); + extVehicle.minSafeTrafficImprovement = Mathf.Lerp(dls.MinMinSafeTrafficImprovement, dls.MaxMinSafeTrafficImprovement, altruism); + } else { + extVehicle.maxReservedSpace = extVehicle.recklessDriver ? dls.MaxRecklessReservedSpace : dls.MaxReservedSpace; + extVehicle.laneSpeedRandInterval = dls.LaneSpeedRandInterval; + extVehicle.maxOptLaneChanges = dls.MaxOptLaneChanges; + extVehicle.maxUnsafeSpeedDiff = dls.MaxUnsafeSpeedDiff; + extVehicle.minSafeSpeedImprovement = dls.MinSafeSpeedImprovement; + extVehicle.minSafeTrafficImprovement = dls.MinSafeTrafficImprovement; + } + extVehicle.dlsReady = true; + } + + private static ushort GetTransitNodeId(ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { + bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); + ushort transitNodeId1 = 0; + Constants.ServiceFactory.NetService.ProcessSegment(curPos.m_segment, delegate (ushort segmentId, ref NetSegment segment) { + transitNodeId1 = startNode ? segment.m_startNode : segment.m_endNode; + return true; + }); + + ushort transitNodeId2 = 0; + Constants.ServiceFactory.NetService.ProcessSegment(nextPos.m_segment, delegate (ushort segmentId, ref NetSegment segment) { + transitNodeId2 = startNode ? segment.m_startNode : segment.m_endNode; + return true; + }); + + if (transitNodeId1 != transitNodeId2) { + return 0; + } + return transitNodeId1; + } + + private static bool IsTransitNodeCurStartNode(ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { + // note: does not check if curPos and nextPos are successive path positions + bool startNode; + if (curPos.m_offset == 0) { + startNode = true; + } else if (curPos.m_offset == 255) { + startNode = false; + } else if (nextPos.m_offset == 0) { + startNode = true; + } else { + startNode = false; + } + return startNode; + } + + private static uint Now() { + return Constants.ServiceFactory.SimulationService.CurrentFrameIndex; + } + + private void DetermineVehicleType(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { + VehicleAI ai = vehicleData.Info.m_vehicleAI; + + if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { + extVehicle.vehicleType = API.Traffic.Enums.ExtVehicleType.Emergency; + } else { + ExtVehicleType? type = DetermineVehicleTypeFromAIType(extVehicle.vehicleId, ai, false); + if (type != null) { + extVehicle.vehicleType = (API.Traffic.Enums.ExtVehicleType)type; + } else { + extVehicle.vehicleType = API.Traffic.Enums.ExtVehicleType.None; + } + } + + if (extVehicle.vehicleType == API.Traffic.Enums.ExtVehicleType.CargoTruck) { + extVehicle.heavyVehicle = ((CargoTruckAI)ai).m_isHeavyVehicle; + } else { + extVehicle.heavyVehicle = false; + } + +#if DEBUG + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.DetermineVehicleType({extVehicle.vehicleId}): vehicleType={extVehicle.vehicleType}, heavyVehicle={extVehicle.heavyVehicle}. Info={vehicleData.Info?.name}"); +#endif + } + + private ExtVehicleType? DetermineVehicleTypeFromAIType(ushort vehicleId, VehicleAI ai, bool emergencyOnDuty) { + if (emergencyOnDuty) + return ExtVehicleType.Emergency; + + switch (ai.m_info.m_vehicleType) { + case VehicleInfo.VehicleType.Bicycle: + return ExtVehicleType.Bicycle; + case VehicleInfo.VehicleType.Car: + if (ai is PassengerCarAI) + return ExtVehicleType.PassengerCar; + if (ai is AmbulanceAI || ai is FireTruckAI || ai is PoliceCarAI || ai is HearseAI || ai is GarbageTruckAI || ai is MaintenanceTruckAI || ai is SnowTruckAI || ai is WaterTruckAI || ai is DisasterResponseVehicleAI || ai is ParkMaintenanceVehicleAI || ai is PostVanAI) { + return ExtVehicleType.Service; + } + if (ai is CarTrailerAI) + return ExtVehicleType.None; + if (ai is BusAI) + return ExtVehicleType.Bus; + if (ai is TaxiAI) + return ExtVehicleType.Taxi; + if (ai is CargoTruckAI) + return ExtVehicleType.CargoTruck; + break; + case VehicleInfo.VehicleType.Metro: + case VehicleInfo.VehicleType.Train: + case VehicleInfo.VehicleType.Monorail: + if (ai is CargoTrainAI) + return ExtVehicleType.CargoTrain; + return ExtVehicleType.PassengerTrain; + case VehicleInfo.VehicleType.Tram: + return ExtVehicleType.Tram; + case VehicleInfo.VehicleType.Ship: + if (ai is PassengerShipAI) + return ExtVehicleType.PassengerShip; + //if (ai is CargoShipAI) + return ExtVehicleType.CargoShip; + //break; + case VehicleInfo.VehicleType.Plane: + if (ai is PassengerPlaneAI) + return ExtVehicleType.PassengerPlane; + if (ai is CargoPlaneAI) + return ExtVehicleType.CargoPlane; + break; + case VehicleInfo.VehicleType.Helicopter: + //if (ai is PassengerPlaneAI) + return ExtVehicleType.Helicopter; + //break; + case VehicleInfo.VehicleType.Ferry: + return ExtVehicleType.Ferry; + case VehicleInfo.VehicleType.Blimp: + return ExtVehicleType.Blimp; + case VehicleInfo.VehicleType.CableCar: + return ExtVehicleType.CableCar; + } #if DEBUGVSTATE - Log._Debug($"ExtVehicleManager.DetermineVehicleType({vehicleId}): Could not determine vehicle type from ai type: {ai.GetType().ToString()}"); -#endif - return null; - } - - public void InitAllVehicles() { - Log._Debug("ExtVehicleManager: InitAllVehicles()"); - VehicleManager vehicleManager = Singleton.instance; - - for (uint vehicleId = 0; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++vehicleId) { - Services.VehicleService.ProcessVehicle((ushort)vehicleId, delegate (ushort vId, ref Vehicle vehicle) { - if ((vehicle.m_flags & Vehicle.Flags.Created) == 0) { - return true; - } - - OnCreateVehicle(vId, ref vehicle); - - if ((vehicle.m_flags & Vehicle.Flags.Emergency2) != 0) { - OnStartPathFind(vId, ref vehicle, ExtVehicleType.Emergency); - } - - if ((vehicle.m_flags & Vehicle.Flags.Spawned) == 0) { - return true; - } - - OnSpawnVehicle(vId, ref vehicle); - - return true; - }); - } - } - - public ushort GetFrontVehicleId(ushort vehicleId, ref Vehicle vehicleData) { - bool reversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0; - ushort frontVehicleId = vehicleId; - if (reversed) { - frontVehicleId = vehicleData.GetLastVehicle(vehicleId); - } else { - frontVehicleId = vehicleData.GetFirstVehicle(vehicleId); - } - - return frontVehicleId; - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - for (int i = 0; i < ExtVehicles.Length; ++i) { - Services.VehicleService.ProcessVehicle((ushort)i, (ushort vehId, ref Vehicle veh) => { - OnRelease(ref ExtVehicles[i], ref veh); - return true; - }); - } - } - - public override void OnAfterLoadData() { - base.OnAfterLoadData(); - InitAllVehicles(); - } - } -} + Log._Debug($"ExtVehicleManager.DetermineVehicleType({vehicleId}): Could not determine vehicle type from ai type: {ai.GetType().ToString()}"); +#endif + return null; + } + + public void InitAllVehicles() { + Log._Debug("ExtVehicleManager: InitAllVehicles()"); + VehicleManager vehicleManager = Singleton.instance; + + for (uint vehicleId = 0; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++vehicleId) { + Services.VehicleService.ProcessVehicle((ushort)vehicleId, delegate (ushort vId, ref Vehicle vehicle) { + if ((vehicle.m_flags & Vehicle.Flags.Created) == 0) { + return true; + } + + OnCreateVehicle(vId, ref vehicle); + + if ((vehicle.m_flags & Vehicle.Flags.Emergency2) != 0) { + OnStartPathFind(vId, ref vehicle, ExtVehicleType.Emergency); + } + + if ((vehicle.m_flags & Vehicle.Flags.Spawned) == 0) { + return true; + } + + OnSpawnVehicle(vId, ref vehicle); + + return true; + }); + } + } + + public ushort GetFrontVehicleId(ushort vehicleId, ref Vehicle vehicleData) { + bool reversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0; + ushort frontVehicleId = vehicleId; + if (reversed) { + frontVehicleId = vehicleData.GetLastVehicle(vehicleId); + } else { + frontVehicleId = vehicleData.GetFirstVehicle(vehicleId); + } + + return frontVehicleId; + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + for (int i = 0; i < ExtVehicles.Length; ++i) { + Services.VehicleService.ProcessVehicle((ushort)i, (ushort vehId, ref Vehicle veh) => { + OnRelease(ref ExtVehicles[i], ref veh); + return true; + }); + } + } + + public override void OnAfterLoadData() { + base.OnAfterLoadData(); + InitAllVehicles(); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/LaneArrowManager.cs b/TLM/TLM/Manager/Impl/LaneArrowManager.cs index 3a26a6f19..f161f9d95 100644 --- a/TLM/TLM/Manager/Impl/LaneArrowManager.cs +++ b/TLM/TLM/Manager/Impl/LaneArrowManager.cs @@ -1,174 +1,173 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Util; -using TrafficManager.State; -using ColossalFramework; -using TrafficManager.Geometry; -using static TrafficManager.State.Flags; -using CSUtil.Commons; -using TrafficManager.Geometry.Impl; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -namespace TrafficManager.Manager.Impl { - public class LaneArrowManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager, ILaneArrowManager { - public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car; - public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.RoadVehicle &~ ExtVehicleType.Emergency; - - public static readonly LaneArrowManager Instance = new LaneArrowManager(); - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"- Not implemented -"); - // TODO implement - } - - public LaneArrows GetFinalLaneArrows(uint laneId) { - return Flags.getFinalLaneArrowFlags(laneId, true); - } - - public bool SetLaneArrows(uint laneId, LaneArrows flags, bool overrideHighwayArrows = false) { - if (Flags.setLaneArrowFlags(laneId, flags, overrideHighwayArrows)) { - OnLaneChange(laneId); - return true; - } - return false; - } - - public bool ToggleLaneArrows(uint laneId, bool startNode, LaneArrows flags, out SetLaneArrowUnableReason res) { - if (Flags.toggleLaneArrowFlags(laneId, startNode, flags, out res)) { - OnLaneChange(laneId); - return true; - } - return false; - } - - protected void OnLaneChange(uint laneId) { - Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { - RoutingManager.Instance.RequestRecalculation(lane.m_segment); - - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(lane.m_segment); - } - return true; - }); - } - - protected override void HandleInvalidSegment(ref ExtSegment seg) { - Flags.resetSegmentArrowFlags(seg.segmentId); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - - } - - public void ApplyFlags() { - for (uint laneId = 0; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { - Flags.applyLaneArrowFlags(laneId); - } - } - - public override void OnBeforeSaveData() { - base.OnBeforeSaveData(); - ApplyFlags(); - } - - public override void OnAfterLoadData() { - base.OnAfterLoadData(); - Flags.clearHighwayLaneArrows(); - ApplyFlags(); - } - - [Obsolete] - public bool LoadData(string data) { - bool success = true; - Log.Info($"Loading lane arrow data (old method)"); +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using State; + using Traffic.Data; + using Traffic.Enums; + + public class LaneArrowManager : AbstractGeometryObservingManager, + ICustomDataManager>, + ICustomDataManager, ILaneArrowManager { + public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + + public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car; + + public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.RoadVehicle &~ ExtVehicleType.Emergency; + + public static readonly LaneArrowManager Instance = new LaneArrowManager(); + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"- Not implemented -"); + // TODO implement + } + + public LaneArrows GetFinalLaneArrows(uint laneId) { + return Flags.getFinalLaneArrowFlags(laneId, true); + } + + public bool SetLaneArrows(uint laneId, LaneArrows flags, bool overrideHighwayArrows = false) { + if (Flags.setLaneArrowFlags(laneId, flags, overrideHighwayArrows)) { + OnLaneChange(laneId); + return true; + } + return false; + } + + public bool ToggleLaneArrows(uint laneId, bool startNode, LaneArrows flags, out SetLaneArrowUnableReason res) { + if (Flags.toggleLaneArrowFlags(laneId, startNode, flags, out res)) { + OnLaneChange(laneId); + return true; + } + return false; + } + + protected void OnLaneChange(uint laneId) { + Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { + RoutingManager.Instance.RequestRecalculation(lane.m_segment); + + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(lane.m_segment); + } + return true; + }); + } + + protected override void HandleInvalidSegment(ref ExtSegment seg) { + Flags.resetSegmentArrowFlags(seg.segmentId); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + + } + + public void ApplyFlags() { + for (uint laneId = 0; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { + Flags.applyLaneArrowFlags(laneId); + } + } + + public override void OnBeforeSaveData() { + base.OnBeforeSaveData(); + ApplyFlags(); + } + + public override void OnAfterLoadData() { + base.OnAfterLoadData(); + Flags.clearHighwayLaneArrows(); + ApplyFlags(); + } + + [Obsolete] + public bool LoadData(string data) { + bool success = true; + Log.Info($"Loading lane arrow data (old method)"); #if DEBUGLOAD Log._Debug($"LaneFlags: {data}"); #endif - var lanes = data.Split(','); + var lanes = data.Split(','); - if (lanes.Length > 1) { - foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1)) { - try { + if (lanes.Length > 1) { + foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1)) { + try { #if DEBUGLOAD Log._Debug($"Split Data: {split[0]} , {split[1]}"); #endif - var laneId = Convert.ToUInt32(split[0]); - uint flags = Convert.ToUInt32(split[1]); + var laneId = Convert.ToUInt32(split[0]); + uint flags = Convert.ToUInt32(split[1]); - if (!Services.NetService.IsLaneValid(laneId)) - continue; + if (!Services.NetService.IsLaneValid(laneId)) + continue; - if (flags > ushort.MaxValue) - continue; - - uint laneArrowFlags = flags & Flags.lfr; - uint origFlags = (Singleton.instance.m_lanes.m_buffer[laneId].m_flags & Flags.lfr); + if (flags > ushort.MaxValue) + continue; + + uint laneArrowFlags = flags & Flags.lfr; + uint origFlags = (Singleton.instance.m_lanes.m_buffer[laneId].m_flags & Flags.lfr); #if DEBUGLOAD Log._Debug("Setting flags for lane " + laneId + " to " + flags + " (" + ((Flags.LaneArrows)(laneArrowFlags)).ToString() + ")"); if ((origFlags | laneArrowFlags) == origFlags) { // only load if setting differs from default Log._Debug("Flags for lane " + laneId + " are original (" + ((NetLane.Flags)(origFlags)).ToString() + ")"); } #endif - SetLaneArrows(laneId, (LaneArrows)laneArrowFlags); - } catch (Exception e) { - Log.Error($"Error loading Lane Split data. Length: {split.Length} value: {split}\nError: {e.ToString()}"); - success = false; - } - } - } - return success; - } - - [Obsolete] - string ICustomDataManager.SaveData(ref bool success) { - return null; - } - - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading lane arrow data (new method)"); - - foreach (var laneArrowData in data) { - try { - if (!Services.NetService.IsLaneValid(laneArrowData.laneId)) - continue; - - uint laneArrowFlags = laneArrowData.arrows & Flags.lfr; - SetLaneArrows(laneArrowData.laneId, (LaneArrows)laneArrowFlags); - } catch (Exception e) { - Log.Error($"Error loading lane arrow data for lane {laneArrowData.laneId}, arrows={laneArrowData.arrows}: {e.ToString()}"); - success = false; - } - } - return success; - } - - public List SaveData(ref bool success) { - List ret = new List(); - for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { - try { - LaneArrows? laneArrows = Flags.getLaneArrowFlags(i); - - if (laneArrows == null) - continue; - - uint laneArrowInt = (uint)laneArrows; + SetLaneArrows(laneId, (LaneArrows)laneArrowFlags); + } catch (Exception e) { + Log.Error($"Error loading Lane Split data. Length: {split.Length} value: {split}\nError: {e.ToString()}"); + success = false; + } + } + } + return success; + } + + [Obsolete] + string ICustomDataManager.SaveData(ref bool success) { + return null; + } + + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading lane arrow data (new method)"); + + foreach (var laneArrowData in data) { + try { + if (!Services.NetService.IsLaneValid(laneArrowData.laneId)) + continue; + + uint laneArrowFlags = laneArrowData.arrows & Flags.lfr; + SetLaneArrows(laneArrowData.laneId, (LaneArrows)laneArrowFlags); + } catch (Exception e) { + Log.Error($"Error loading lane arrow data for lane {laneArrowData.laneId}, arrows={laneArrowData.arrows}: {e.ToString()}"); + success = false; + } + } + return success; + } + + public List SaveData(ref bool success) { + List ret = new List(); + for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { + try { + LaneArrows? laneArrows = Flags.getLaneArrowFlags(i); + + if (laneArrows == null) + continue; + + uint laneArrowInt = (uint)laneArrows; #if DEBUGSAVE Log._Debug($"Saving lane arrows for lane {i}, setting to {laneArrows.ToString()} ({laneArrowInt})"); #endif - ret.Add(new Configuration.LaneArrowData(i, laneArrowInt)); - } catch (Exception e) { - Log.Error($"Exception occurred while saving lane arrows @ {i}: {e.ToString()}"); - success = false; - } - } - return ret; - } - } -} + ret.Add(new Configuration.LaneArrowData(i, laneArrowInt)); + } catch (Exception e) { + Log.Error($"Exception occurred while saving lane arrows @ {i}: {e.ToString()}"); + success = false; + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/LaneConnectionManager.cs b/TLM/TLM/Manager/Impl/LaneConnectionManager.cs index 9bb00a03a..7117f8066 100644 --- a/TLM/TLM/Manager/Impl/LaneConnectionManager.cs +++ b/TLM/TLM/Manager/Impl/LaneConnectionManager.cs @@ -1,668 +1,669 @@ -using ColossalFramework; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using TrafficManager.Geometry; -using TrafficManager.Traffic; -using TrafficManager.State; -using TrafficManager.Util; -using UnityEngine; -using CSUtil.Commons; -using TrafficManager.Geometry.Impl; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -namespace TrafficManager.Manager.Impl { - public class LaneConnectionManager : AbstractGeometryObservingManager, ICustomDataManager>, ILaneConnectionManager { - public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail; - public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.RoadVehicle | ExtVehicleType.Tram | ExtVehicleType.RailVehicle; - - public static LaneConnectionManager Instance { get; private set; } = null; - - static LaneConnectionManager() { - Instance = new LaneConnectionManager(); - } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"- Not implemented -"); - // TODO implement - } - - /// - /// Checks if traffic may flow from source lane to target lane according to setup lane connections - /// - /// - /// - /// (optional) check at start node of source lane? - /// - public bool AreLanesConnected(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { - if (! Options.laneConnectorEnabled) { - return true; - } - - if (targetLaneId == 0 || Flags.laneConnections[sourceLaneId] == null) { - return false; - } - - int nodeArrayIndex = sourceStartNode ? 0 : 1; - - uint[] connectedLanes = Flags.laneConnections[sourceLaneId][nodeArrayIndex]; - if (connectedLanes == null) { - return false; - } - - bool ret = false; - foreach (uint laneId in connectedLanes) - if (laneId == targetLaneId) { - ret = true; - break; - } - - return ret; - } - - /// - /// Determines if the given lane has outgoing connections - /// - /// - /// - public bool HasConnections(uint sourceLaneId, bool startNode) { - if (!Options.laneConnectorEnabled) { - return false; - } - - int nodeArrayIndex = startNode ? 0 : 1; - - bool ret = Flags.laneConnections[sourceLaneId] != null && Flags.laneConnections[sourceLaneId][nodeArrayIndex] != null; - return ret; - } - - /// - /// Determines if there exist custom lane connections at the specified node - /// - /// - public bool HasNodeConnections(ushort nodeId) { - if (!Options.laneConnectorEnabled) { - return false; - } - - bool ret = false; - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment seg, byte laneIndex) { - if (HasConnections(laneId, seg.m_startNode == nodeId)) { - ret = true; - return false; - } - return true; - }); - return !ret; - }); - return ret; - } - - public bool HasUturnConnections(ushort segmentId, bool startNode) { - if (!Options.laneConnectorEnabled) { - return false; - } - - NetManager netManager = Singleton.instance; - int nodeArrayIndex = startNode ? 0 : 1; - - uint sourceLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - while (sourceLaneId != 0) { - uint[] targetLaneIds = GetLaneConnections(sourceLaneId, startNode); - - if (targetLaneIds != null) { - foreach (uint targetLaneId in targetLaneIds) { - if (netManager.m_lanes.m_buffer[targetLaneId].m_segment == segmentId) { - return true; - } - } - } - sourceLaneId = netManager.m_lanes.m_buffer[sourceLaneId].m_nextLane; - } - return false; - } - - internal int CountConnections(uint sourceLaneId, bool startNode) { - if (!Options.laneConnectorEnabled) { - return 0; - } - - if (Flags.laneConnections[sourceLaneId] == null) - return 0; - int nodeArrayIndex = startNode ? 0 : 1; - if (Flags.laneConnections[sourceLaneId][nodeArrayIndex] == null) - return 0; - - return Flags.laneConnections[sourceLaneId][nodeArrayIndex].Length; - } - - /// - /// Gets all lane connections for the given lane - /// - /// - /// - internal uint[] GetLaneConnections(uint laneId, bool startNode) { - if (!Options.laneConnectorEnabled) { - return null; - } - - if (Flags.laneConnections[laneId] == null) - return null; - - int nodeArrayIndex = startNode ? 0 : 1; - return Flags.laneConnections[laneId][nodeArrayIndex]; - } - - /// - /// Removes a lane connection between two lanes - /// - /// - /// - /// - /// - internal bool RemoveLaneConnection(uint laneId1, uint laneId2, bool startNode1) { +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using State; + using Traffic.Data; + using UnityEngine; + + public class LaneConnectionManager : AbstractGeometryObservingManager, + ICustomDataManager>, + ILaneConnectionManager { + public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + + public const VehicleInfo.VehicleType VEHICLE_TYPES = + VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram + | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail; + + public const ExtVehicleType EXT_VEHICLE_TYPES = + ExtVehicleType.RoadVehicle | ExtVehicleType.Tram | ExtVehicleType.RailVehicle; + + public static LaneConnectionManager Instance { get; private set; } = null; + + static LaneConnectionManager() { + Instance = new LaneConnectionManager(); + } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"- Not implemented -"); + // TODO implement + } + + /// + /// Checks if traffic may flow from source lane to target lane according to setup lane connections + /// + /// + /// + /// (optional) check at start node of source lane? + /// + public bool AreLanesConnected(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { + if (! Options.laneConnectorEnabled) { + return true; + } + + if (targetLaneId == 0 || Flags.laneConnections[sourceLaneId] == null) { + return false; + } + + int nodeArrayIndex = sourceStartNode ? 0 : 1; + + uint[] connectedLanes = Flags.laneConnections[sourceLaneId][nodeArrayIndex]; + if (connectedLanes == null) { + return false; + } + + bool ret = false; + foreach (uint laneId in connectedLanes) + if (laneId == targetLaneId) { + ret = true; + break; + } + + return ret; + } + + /// + /// Determines if the given lane has outgoing connections + /// + /// + /// + public bool HasConnections(uint sourceLaneId, bool startNode) { + if (!Options.laneConnectorEnabled) { + return false; + } + + int nodeArrayIndex = startNode ? 0 : 1; + + bool ret = Flags.laneConnections[sourceLaneId] != null && Flags.laneConnections[sourceLaneId][nodeArrayIndex] != null; + return ret; + } + + /// + /// Determines if there exist custom lane connections at the specified node + /// + /// + public bool HasNodeConnections(ushort nodeId) { + if (!Options.laneConnectorEnabled) { + return false; + } + + bool ret = false; + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment seg, byte laneIndex) { + if (HasConnections(laneId, seg.m_startNode == nodeId)) { + ret = true; + return false; + } + return true; + }); + return !ret; + }); + return ret; + } + + public bool HasUturnConnections(ushort segmentId, bool startNode) { + if (!Options.laneConnectorEnabled) { + return false; + } + + NetManager netManager = Singleton.instance; + int nodeArrayIndex = startNode ? 0 : 1; + + uint sourceLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + while (sourceLaneId != 0) { + uint[] targetLaneIds = GetLaneConnections(sourceLaneId, startNode); + + if (targetLaneIds != null) { + foreach (uint targetLaneId in targetLaneIds) { + if (netManager.m_lanes.m_buffer[targetLaneId].m_segment == segmentId) { + return true; + } + } + } + sourceLaneId = netManager.m_lanes.m_buffer[sourceLaneId].m_nextLane; + } + return false; + } + + internal int CountConnections(uint sourceLaneId, bool startNode) { + if (!Options.laneConnectorEnabled) { + return 0; + } + + if (Flags.laneConnections[sourceLaneId] == null) + return 0; + int nodeArrayIndex = startNode ? 0 : 1; + if (Flags.laneConnections[sourceLaneId][nodeArrayIndex] == null) + return 0; + + return Flags.laneConnections[sourceLaneId][nodeArrayIndex].Length; + } + + /// + /// Gets all lane connections for the given lane + /// + /// + /// + internal uint[] GetLaneConnections(uint laneId, bool startNode) { + if (!Options.laneConnectorEnabled) { + return null; + } + + if (Flags.laneConnections[laneId] == null) + return null; + + int nodeArrayIndex = startNode ? 0 : 1; + return Flags.laneConnections[laneId][nodeArrayIndex]; + } + + /// + /// Removes a lane connection between two lanes + /// + /// + /// + /// + /// + internal bool RemoveLaneConnection(uint laneId1, uint laneId2, bool startNode1) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.RemoveLaneConnection({laneId1}, {laneId2}, {startNode1}) called."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.RemoveLaneConnection({laneId1}, {laneId2}, {startNode1}) called."); #endif - bool ret = Flags.RemoveLaneConnection(laneId1, laneId2, startNode1); + bool ret = Flags.RemoveLaneConnection(laneId1, laneId2, startNode1); #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RemoveLaneConnection({laneId1}, {laneId2}, {startNode1}): ret={ret}"); + if (debug) + Log._Debug($"LaneConnectionManager.RemoveLaneConnection({laneId1}, {laneId2}, {startNode1}): ret={ret}"); #endif - if (ret) { - NetManager netManager = Singleton.instance; - ushort segmentId1 = netManager.m_lanes.m_buffer[laneId1].m_segment; - ushort segmentId2 = netManager.m_lanes.m_buffer[laneId2].m_segment; - - ushort commonNodeId; - bool startNode2; - GetCommonNodeId(laneId1, laneId2, startNode1, out commonNodeId, out startNode2); - - RecalculateLaneArrows(laneId1, commonNodeId, startNode1); - RecalculateLaneArrows(laneId2, commonNodeId, startNode2); - - RoutingManager.Instance.RequestRecalculation(segmentId1, false); - RoutingManager.Instance.RequestRecalculation(segmentId2, false); - - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(segmentId1); - Services.NetService.PublishSegmentChanges(segmentId2); - } - } - - return ret; - } - - /// - /// Removes all lane connections at the specified node - /// - /// - internal void RemoveLaneConnectionsFromNode(ushort nodeId) { + if (ret) { + NetManager netManager = Singleton.instance; + ushort segmentId1 = netManager.m_lanes.m_buffer[laneId1].m_segment; + ushort segmentId2 = netManager.m_lanes.m_buffer[laneId2].m_segment; + + ushort commonNodeId; + bool startNode2; + GetCommonNodeId(laneId1, laneId2, startNode1, out commonNodeId, out startNode2); + + RecalculateLaneArrows(laneId1, commonNodeId, startNode1); + RecalculateLaneArrows(laneId2, commonNodeId, startNode2); + + RoutingManager.Instance.RequestRecalculation(segmentId1, false); + RoutingManager.Instance.RequestRecalculation(segmentId2, false); + + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(segmentId1); + Services.NetService.PublishSegmentChanges(segmentId2); + } + } + + return ret; + } + + /// + /// Removes all lane connections at the specified node + /// + /// + internal void RemoveLaneConnectionsFromNode(ushort nodeId) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromNode({nodeId}) called."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromNode({nodeId}) called."); #endif - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - RemoveLaneConnectionsFromSegment(segmentId, segment.m_startNode == nodeId); - return true; - }); - } - - /// - /// Removes all lane connections at the specified segment end - /// - /// - /// - internal void RemoveLaneConnectionsFromSegment(ushort segmentId, bool startNode, bool recalcAndPublish=true) { + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + RemoveLaneConnectionsFromSegment(segmentId, segment.m_startNode == nodeId); + return true; + }); + } + + /// + /// Removes all lane connections at the specified segment end + /// + /// + /// + internal void RemoveLaneConnectionsFromSegment(ushort segmentId, bool startNode, bool recalcAndPublish=true) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromSegment({segmentId}, {startNode}) called."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromSegment({segmentId}, {startNode}) called."); #endif - Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { + Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromSegment: Removing lane connections from segment {segmentId}, lane {laneId}."); + if (debug) + Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromSegment: Removing lane connections from segment {segmentId}, lane {laneId}."); #endif - RemoveLaneConnections(laneId, startNode, false); - return true; - }); - - if (recalcAndPublish) { - RoutingManager.Instance.RequestRecalculation(segmentId); - - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(segmentId); - } - } - } - - /// - /// Removes all lane connections from the specified lane - /// - /// - /// - internal void RemoveLaneConnections(uint laneId, bool startNode, bool recalcAndPublish=true) { + RemoveLaneConnections(laneId, startNode, false); + return true; + }); + + if (recalcAndPublish) { + RoutingManager.Instance.RequestRecalculation(segmentId); + + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(segmentId); + } + } + } + + /// + /// Removes all lane connections from the specified lane + /// + /// + /// + internal void RemoveLaneConnections(uint laneId, bool startNode, bool recalcAndPublish=true) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.RemoveLaneConnections({laneId}, {startNode}) called."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.RemoveLaneConnections({laneId}, {startNode}) called."); #endif - if (Flags.laneConnections[laneId] == null) - return; + if (Flags.laneConnections[laneId] == null) + return; - int nodeArrayIndex = startNode ? 0 : 1; + int nodeArrayIndex = startNode ? 0 : 1; - if (Flags.laneConnections[laneId][nodeArrayIndex] == null) - return; + if (Flags.laneConnections[laneId][nodeArrayIndex] == null) + return; - NetManager netManager = Singleton.instance; + NetManager netManager = Singleton.instance; - /*for (int i = 0; i < Flags.laneConnections[laneId][nodeArrayIndex].Length; ++i) { - uint otherLaneId = Flags.laneConnections[laneId][nodeArrayIndex][i]; - if (Flags.laneConnections[otherLaneId] != null) { - if ((Flags.laneConnections[otherLaneId][0] != null && Flags.laneConnections[otherLaneId][0].Length == 1 && Flags.laneConnections[otherLaneId][0][0] == laneId && Flags.laneConnections[otherLaneId][1] == null) || - Flags.laneConnections[otherLaneId][1] != null && Flags.laneConnections[otherLaneId][1].Length == 1 && Flags.laneConnections[otherLaneId][1][0] == laneId && Flags.laneConnections[otherLaneId][0] == null) { + /*for (int i = 0; i < Flags.laneConnections[laneId][nodeArrayIndex].Length; ++i) { + uint otherLaneId = Flags.laneConnections[laneId][nodeArrayIndex][i]; + if (Flags.laneConnections[otherLaneId] != null) { + if ((Flags.laneConnections[otherLaneId][0] != null && Flags.laneConnections[otherLaneId][0].Length == 1 && Flags.laneConnections[otherLaneId][0][0] == laneId && Flags.laneConnections[otherLaneId][1] == null) || + Flags.laneConnections[otherLaneId][1] != null && Flags.laneConnections[otherLaneId][1].Length == 1 && Flags.laneConnections[otherLaneId][1][0] == laneId && Flags.laneConnections[otherLaneId][0] == null) { - ushort otherSegmentId = netManager.m_lanes.m_buffer[otherLaneId].m_segment; - UnsubscribeFromSegmentGeometry(otherSegmentId); - } - } - }*/ + ushort otherSegmentId = netManager.m_lanes.m_buffer[otherLaneId].m_segment; + UnsubscribeFromSegmentGeometry(otherSegmentId); + } + } + }*/ - Flags.RemoveLaneConnections(laneId, startNode); + Flags.RemoveLaneConnections(laneId, startNode); - Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { - if (recalcAndPublish) { - RoutingManager.Instance.RequestRecalculation(lane.m_segment); + Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { + if (recalcAndPublish) { + RoutingManager.Instance.RequestRecalculation(lane.m_segment); - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(lane.m_segment); - } - } - return true; - }); - } + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(lane.m_segment); + } + } + return true; + }); + } - /// - /// Adds a lane connection between two lanes - /// - /// - /// - /// - /// - internal bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { - if (sourceLaneId == targetLaneId) { - return false; - } + /// + /// Adds a lane connection between two lanes + /// + /// + /// + /// + /// + internal bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { + if (sourceLaneId == targetLaneId) { + return false; + } - bool ret = Flags.AddLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); + bool ret = Flags.AddLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.AddLaneConnection({sourceLaneId}, {targetLaneId}, {sourceStartNode}): ret={ret}"); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.AddLaneConnection({sourceLaneId}, {targetLaneId}, {sourceStartNode}): ret={ret}"); #endif - if (ret) { - ushort commonNodeId; - bool targetStartNode; - GetCommonNodeId(sourceLaneId, targetLaneId, sourceStartNode, out commonNodeId, out targetStartNode); - RecalculateLaneArrows(sourceLaneId, commonNodeId, sourceStartNode); - RecalculateLaneArrows(targetLaneId, commonNodeId, targetStartNode); + if (ret) { + ushort commonNodeId; + bool targetStartNode; + GetCommonNodeId(sourceLaneId, targetLaneId, sourceStartNode, out commonNodeId, out targetStartNode); + RecalculateLaneArrows(sourceLaneId, commonNodeId, sourceStartNode); + RecalculateLaneArrows(targetLaneId, commonNodeId, targetStartNode); - NetManager netManager = Singleton.instance; + NetManager netManager = Singleton.instance; - ushort sourceSegmentId = netManager.m_lanes.m_buffer[sourceLaneId].m_segment; - ushort targetSegmentId = netManager.m_lanes.m_buffer[targetLaneId].m_segment; + ushort sourceSegmentId = netManager.m_lanes.m_buffer[sourceLaneId].m_segment; + ushort targetSegmentId = netManager.m_lanes.m_buffer[targetLaneId].m_segment; - if (sourceSegmentId == targetSegmentId) { - JunctionRestrictionsManager.Instance.SetUturnAllowed(sourceSegmentId, sourceStartNode, true); - } + if (sourceSegmentId == targetSegmentId) { + JunctionRestrictionsManager.Instance.SetUturnAllowed(sourceSegmentId, sourceStartNode, true); + } - RoutingManager.Instance.RequestRecalculation(sourceSegmentId, false); - RoutingManager.Instance.RequestRecalculation(targetSegmentId, false); + RoutingManager.Instance.RequestRecalculation(sourceSegmentId, false); + RoutingManager.Instance.RequestRecalculation(targetSegmentId, false); - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(sourceSegmentId); - Services.NetService.PublishSegmentChanges(targetSegmentId); - } - } + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(sourceSegmentId); + Services.NetService.PublishSegmentChanges(targetSegmentId); + } + } - return ret; - } + return ret; + } - protected override void HandleInvalidSegment(ref ExtSegment seg) { + protected override void HandleInvalidSegment(ref ExtSegment seg) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.HandleInvalidSegment({seg.segmentId}): Segment has become invalid. Removing lane connections."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.HandleInvalidSegment({seg.segmentId}): Segment has become invalid. Removing lane connections."); #endif - RemoveLaneConnectionsFromSegment(seg.segmentId, false, false); - RemoveLaneConnectionsFromSegment(seg.segmentId, true); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - - } - - /// - /// Given two lane ids and node of the first lane, determines the node id to which both lanes are connected to - /// - /// - /// - /// - internal void GetCommonNodeId(uint laneId1, uint laneId2, bool startNode1, out ushort commonNodeId, out bool startNode2) { - NetManager netManager = Singleton.instance; - ushort segmentId1 = netManager.m_lanes.m_buffer[laneId1].m_segment; - ushort segmentId2 = netManager.m_lanes.m_buffer[laneId2].m_segment; - - ushort nodeId2Start = netManager.m_segments.m_buffer[segmentId2].m_startNode; - ushort nodeId2End = netManager.m_segments.m_buffer[segmentId2].m_endNode; - - ushort nodeId1 = startNode1 ? netManager.m_segments.m_buffer[segmentId1].m_startNode : netManager.m_segments.m_buffer[segmentId1].m_endNode; - - startNode2 = (nodeId1 == nodeId2Start); - if (!startNode2 && nodeId1 != nodeId2End) - commonNodeId = 0; - else - commonNodeId = nodeId1; - } - - internal bool GetLaneEndPoint(ushort segmentId, bool startNode, byte laneIndex, uint? laneId, NetInfo.Lane laneInfo, out bool outgoing, out bool incoming, out Vector3? pos) { - NetManager netManager = Singleton.instance; - - pos = null; - outgoing = false; - incoming = false; - - if ((netManager.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return false; - - if (laneId == null) { - laneId = FindLaneId(segmentId, laneIndex); - if (laneId == null) - return false; - } - - if ((netManager.m_lanes.m_buffer[(uint)laneId].m_flags & ((ushort)NetLane.Flags.Created | (ushort)NetLane.Flags.Deleted)) != (ushort)NetLane.Flags.Created) - return false; - - if (laneInfo == null) { - if (laneIndex < netManager.m_segments.m_buffer[segmentId].Info.m_lanes.Length) - laneInfo = netManager.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex]; - else - return false; - } - - NetInfo.Direction laneDir = ((NetManager.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); - - if (startNode) { - if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) - outgoing = true; - if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) - incoming = true; - pos = NetManager.instance.m_lanes.m_buffer[(uint)laneId].m_bezier.a; - } else { - if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) - outgoing = true; - if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) - incoming = true; - pos = NetManager.instance.m_lanes.m_buffer[(uint)laneId].m_bezier.d; - } - - return true; - } - - private uint? FindLaneId(ushort segmentId, byte laneIndex) { - NetInfo.Lane[] lanes = NetManager.instance.m_segments.m_buffer[segmentId].Info.m_lanes; - uint laneId = NetManager.instance.m_segments.m_buffer[segmentId].m_lanes; - for (byte i = 0; i < lanes.Length && laneId != 0; i++) { - if (i == laneIndex) - return laneId; - - laneId = NetManager.instance.m_lanes.m_buffer[laneId].m_nextLane; - } - return null; - } - - /// - /// Recalculates lane arrows based on present lane connections. - /// - /// - /// - private void RecalculateLaneArrows(uint laneId, ushort nodeId, bool startNode) { + RemoveLaneConnectionsFromSegment(seg.segmentId, false, false); + RemoveLaneConnectionsFromSegment(seg.segmentId, true); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + + } + + /// + /// Given two lane ids and node of the first lane, determines the node id to which both lanes are connected to + /// + /// + /// + /// + internal void GetCommonNodeId(uint laneId1, uint laneId2, bool startNode1, out ushort commonNodeId, out bool startNode2) { + NetManager netManager = Singleton.instance; + ushort segmentId1 = netManager.m_lanes.m_buffer[laneId1].m_segment; + ushort segmentId2 = netManager.m_lanes.m_buffer[laneId2].m_segment; + + ushort nodeId2Start = netManager.m_segments.m_buffer[segmentId2].m_startNode; + ushort nodeId2End = netManager.m_segments.m_buffer[segmentId2].m_endNode; + + ushort nodeId1 = startNode1 ? netManager.m_segments.m_buffer[segmentId1].m_startNode : netManager.m_segments.m_buffer[segmentId1].m_endNode; + + startNode2 = (nodeId1 == nodeId2Start); + if (!startNode2 && nodeId1 != nodeId2End) + commonNodeId = 0; + else + commonNodeId = nodeId1; + } + + internal bool GetLaneEndPoint(ushort segmentId, bool startNode, byte laneIndex, uint? laneId, NetInfo.Lane laneInfo, out bool outgoing, out bool incoming, out Vector3? pos) { + NetManager netManager = Singleton.instance; + + pos = null; + outgoing = false; + incoming = false; + + if ((netManager.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return false; + + if (laneId == null) { + laneId = FindLaneId(segmentId, laneIndex); + if (laneId == null) + return false; + } + + if ((netManager.m_lanes.m_buffer[(uint)laneId].m_flags & ((ushort)NetLane.Flags.Created | (ushort)NetLane.Flags.Deleted)) != (ushort)NetLane.Flags.Created) + return false; + + if (laneInfo == null) { + if (laneIndex < netManager.m_segments.m_buffer[segmentId].Info.m_lanes.Length) + laneInfo = netManager.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex]; + else + return false; + } + + NetInfo.Direction laneDir = ((NetManager.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); + + if (startNode) { + if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) + outgoing = true; + if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) + incoming = true; + pos = NetManager.instance.m_lanes.m_buffer[(uint)laneId].m_bezier.a; + } else { + if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) + outgoing = true; + if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) + incoming = true; + pos = NetManager.instance.m_lanes.m_buffer[(uint)laneId].m_bezier.d; + } + + return true; + } + + private uint? FindLaneId(ushort segmentId, byte laneIndex) { + NetInfo.Lane[] lanes = NetManager.instance.m_segments.m_buffer[segmentId].Info.m_lanes; + uint laneId = NetManager.instance.m_segments.m_buffer[segmentId].m_lanes; + for (byte i = 0; i < lanes.Length && laneId != 0; i++) { + if (i == laneIndex) + return laneId; + + laneId = NetManager.instance.m_lanes.m_buffer[laneId].m_nextLane; + } + return null; + } + + /// + /// Recalculates lane arrows based on present lane connections. + /// + /// + /// + private void RecalculateLaneArrows(uint laneId, ushort nodeId, bool startNode) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}) called"); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}) called"); #endif - if (!Options.laneConnectorEnabled) { - return; - } + if (!Options.laneConnectorEnabled) { + return; + } - if (!Flags.mayHaveLaneArrows(laneId, startNode)) { + if (!Flags.mayHaveLaneArrows(laneId, startNode)) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId}, startNode? {startNode} must not have lane arrows"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId}, startNode? {startNode} must not have lane arrows"); #endif - return; - } + return; + } - if (!HasConnections(laneId, startNode)) { + if (!HasConnections(laneId, startNode)) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId} does not have outgoing connections"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId} does not have outgoing connections"); #endif - return; - } + return; + } - if (nodeId == 0) { + if (nodeId == 0) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node"); #endif - return; - } + return; + } - LaneArrows arrows = LaneArrows.None; + LaneArrows arrows = LaneArrows.None; - NetManager netManager = Singleton.instance; - ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; + NetManager netManager = Singleton.instance; + ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; - if (segmentId == 0) { + if (segmentId == 0) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment"); #endif - return; - } + return; + } #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): startNode? {startNode}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): startNode? {startNode}"); #endif - if (! Services.NetService.IsNodeValid(nodeId)) { + if (! Services.NetService.IsNodeValid(nodeId)) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): Node is invalid"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): Node is invalid"); #endif - return; - } + return; + } - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort otherSegmentId, ref NetSegment otherSeg) { - ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort otherSegmentId, ref NetSegment otherSeg) { + ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}. dir={dir}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}. dir={dir}"); #endif - // check if arrow has already been set for this direction - switch (dir) { - case ArrowDirection.Turn: - if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { - if ((arrows & LaneArrows.Right) != LaneArrows.None) - return true; - } else { - if ((arrows & LaneArrows.Left) != LaneArrows.None) - return true; - } - break; - case ArrowDirection.Forward: - if ((arrows & LaneArrows.Forward) != LaneArrows.None) - return true; - break; - case ArrowDirection.Left: - if ((arrows & LaneArrows.Left) != LaneArrows.None) - return true; - break; - case ArrowDirection.Right: - if ((arrows & LaneArrows.Right) != LaneArrows.None) - return true; - break; - default: - return true; - } + // check if arrow has already been set for this direction + switch (dir) { + case ArrowDirection.Turn: + if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { + if ((arrows & LaneArrows.Right) != LaneArrows.None) + return true; + } else { + if ((arrows & LaneArrows.Left) != LaneArrows.None) + return true; + } + break; + case ArrowDirection.Forward: + if ((arrows & LaneArrows.Forward) != LaneArrows.None) + return true; + break; + case ArrowDirection.Left: + if ((arrows & LaneArrows.Left) != LaneArrows.None) + return true; + break; + case ArrowDirection.Right: + if ((arrows & LaneArrows.Right) != LaneArrows.None) + return true; + break; + default: + return true; + } #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: need to determine arrows"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: need to determine arrows"); #endif - bool addArrow = false; + bool addArrow = false; - uint curLaneId = netManager.m_segments.m_buffer[otherSegmentId].m_lanes; - while (curLaneId != 0) { + uint curLaneId = netManager.m_segments.m_buffer[otherSegmentId].m_lanes; + while (curLaneId != 0) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}"); #endif - if (AreLanesConnected(laneId, curLaneId, startNode)) { + if (AreLanesConnected(laneId, curLaneId, startNode)) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}: lanes are connected"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}: lanes are connected"); #endif - addArrow = true; - break; - } + addArrow = true; + break; + } - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + } #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: finished processing lanes. addArrow={addArrow} arrows (before)={arrows}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: finished processing lanes. addArrow={addArrow} arrows (before)={arrows}"); #endif - if (addArrow) { - switch (dir) { - case ArrowDirection.Turn: - if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { - arrows |= LaneArrows.Right; - } else { - arrows |= LaneArrows.Left; - } - break; - case ArrowDirection.Forward: - arrows |= LaneArrows.Forward; - break; - case ArrowDirection.Left: - arrows |= LaneArrows.Left; - break; - case ArrowDirection.Right: - arrows |= LaneArrows.Right; - break; - default: - return true; - } + if (addArrow) { + switch (dir) { + case ArrowDirection.Turn: + if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { + arrows |= LaneArrows.Right; + } else { + arrows |= LaneArrows.Left; + } + break; + case ArrowDirection.Forward: + arrows |= LaneArrows.Forward; + break; + case ArrowDirection.Left: + arrows |= LaneArrows.Left; + break; + case ArrowDirection.Right: + arrows |= LaneArrows.Right; + break; + default: + return true; + } #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: arrows={arrows}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: arrows={arrows}"); #endif - } + } - return true; - }); + return true; + }); #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): setting lane arrows to {arrows}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): setting lane arrows to {arrows}"); #endif - LaneArrowManager.Instance.SetLaneArrows(laneId, arrows, true); - } - - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading {data.Count} lane connections"); - foreach (Configuration.LaneConnection conn in data) { - try { - if (!Services.NetService.IsLaneValid(conn.lowerLaneId)) - continue; - if (!Services.NetService.IsLaneValid(conn.higherLaneId)) - continue; - if (conn.lowerLaneId == conn.higherLaneId) { - continue; - } + LaneArrowManager.Instance.SetLaneArrows(laneId, arrows, true); + } + + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading {data.Count} lane connections"); + foreach (Configuration.LaneConnection conn in data) { + try { + if (!Services.NetService.IsLaneValid(conn.lowerLaneId)) + continue; + if (!Services.NetService.IsLaneValid(conn.higherLaneId)) + continue; + if (conn.lowerLaneId == conn.higherLaneId) { + continue; + } #if DEBUGLOAD Log._Debug($"Loading lane connection: lane {conn.lowerLaneId} -> {conn.higherLaneId}"); #endif - AddLaneConnection(conn.lowerLaneId, conn.higherLaneId, conn.lowerStartNode); - } catch (Exception e) { - // ignore, as it's probably corrupt save data. it'll be culled on next save - Log.Error("Error loading data from lane connection: " + e.ToString()); - success = false; - } - } - return success; - } - - public List SaveData(ref bool success) { - List ret = new List(); - for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { - try { - if (Flags.laneConnections[i] == null) - continue; - - for (int nodeArrayIndex = 0; nodeArrayIndex <= 1; ++nodeArrayIndex) { - uint[] connectedLaneIds = Flags.laneConnections[i][nodeArrayIndex]; - bool startNode = nodeArrayIndex == 0; - if (connectedLaneIds != null) { - foreach (uint otherHigherLaneId in connectedLaneIds) { - if (otherHigherLaneId <= i) - continue; - if (!Services.NetService.IsLaneValid(otherHigherLaneId)) - continue; + AddLaneConnection(conn.lowerLaneId, conn.higherLaneId, conn.lowerStartNode); + } catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Error("Error loading data from lane connection: " + e.ToString()); + success = false; + } + } + return success; + } + + public List SaveData(ref bool success) { + List ret = new List(); + for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { + try { + if (Flags.laneConnections[i] == null) + continue; + + for (int nodeArrayIndex = 0; nodeArrayIndex <= 1; ++nodeArrayIndex) { + uint[] connectedLaneIds = Flags.laneConnections[i][nodeArrayIndex]; + bool startNode = nodeArrayIndex == 0; + if (connectedLaneIds != null) { + foreach (uint otherHigherLaneId in connectedLaneIds) { + if (otherHigherLaneId <= i) + continue; + if (!Services.NetService.IsLaneValid(otherHigherLaneId)) + continue; #if DEBUGSAVE Log._Debug($"Saving lane connection: lane {i} -> {otherHigherLaneId}"); #endif - ret.Add(new Configuration.LaneConnection(i, (uint)otherHigherLaneId, startNode)); - } - } - } - } catch (Exception e) { - Log.Error($"Exception occurred while saving lane data @ {i}: {e.ToString()}"); - success = false; - } - } - return ret; - } - } -} + ret.Add(new Configuration.LaneConnection(i, (uint)otherHigherLaneId, startNode)); + } + } + } + } catch (Exception e) { + Log.Error($"Exception occurred while saving lane data @ {i}: {e.ToString()}"); + success = false; + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/ManagerFactory.cs b/TLM/TLM/Manager/Impl/ManagerFactory.cs index e99b3ffed..a865bd08d 100644 --- a/TLM/TLM/Manager/Impl/ManagerFactory.cs +++ b/TLM/TLM/Manager/Impl/ManagerFactory.cs @@ -5,6 +5,8 @@ using TrafficManager.Manager; namespace TrafficManager.Manager.Impl { + using API.Manager; + public class ManagerFactory : IManagerFactory { public static IManagerFactory Instance = new ManagerFactory(); diff --git a/TLM/TLM/Manager/Impl/OptionsManager.cs b/TLM/TLM/Manager/Impl/OptionsManager.cs index 3b3098b76..edaecbc34 100644 --- a/TLM/TLM/Manager/Impl/OptionsManager.cs +++ b/TLM/TLM/Manager/Impl/OptionsManager.cs @@ -7,6 +7,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + public class OptionsManager : AbstractCustomManager, IOptionsManager { // TODO I contain ugly code public static OptionsManager Instance = new OptionsManager(); diff --git a/TLM/TLM/Manager/Impl/RoutingManager.cs b/TLM/TLM/Manager/Impl/RoutingManager.cs index 570653cd1..85f624d46 100644 --- a/TLM/TLM/Manager/Impl/RoutingManager.cs +++ b/TLM/TLM/Manager/Impl/RoutingManager.cs @@ -18,6 +18,8 @@ using static TrafficManager.State.Flags; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + public class RoutingManager : AbstractGeometryObservingManager, IRoutingManager { public static readonly RoutingManager Instance = new RoutingManager(); diff --git a/TLM/TLM/Manager/Impl/TrafficLightManager.cs b/TLM/TLM/Manager/Impl/TrafficLightManager.cs index 8faf4f69a..83cd7abb0 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightManager.cs @@ -11,6 +11,8 @@ using TrafficManager.Util; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + /// /// Manages traffic light toggling /// diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs index a6135d5d5..f0210a772 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs @@ -1,710 +1,705 @@ -using System; -using ColossalFramework; -using TrafficManager.Geometry; -using System.Collections.Generic; -using TrafficManager.State; -using TrafficManager.Custom.AI; -using TrafficManager.Util; -using TrafficManager.TrafficLight; -using TrafficManager.Traffic; -using System.Linq; -using CSUtil.Commons; -using TrafficManager.TrafficLight.Impl; -using TrafficManager.Geometry.Impl; -using CSUtil.Commons.Benchmark; -using TrafficManager.TrafficLight.Data; -using TrafficManager.Traffic.Enums; -using static RoadBaseAI; - namespace TrafficManager.Manager.Impl { - public class TrafficLightSimulationManager : AbstractGeometryObservingManager, ICustomDataManager>, ITrafficLightSimulationManager { - public static readonly TrafficLightSimulationManager Instance = new TrafficLightSimulationManager(); - public const int SIM_MOD = 64; - - /// - /// For each node id: traffic light simulation assigned to the node - /// - public TrafficLightSimulation[] TrafficLightSimulations { get; private set; } = null; - //public Dictionary TrafficLightSimulations = new Dictionary(); - - private TrafficLightSimulationManager() { - TrafficLightSimulations = new TrafficLightSimulation[NetManager.MAX_NODE_COUNT]; - for (int i = 0; i < TrafficLightSimulations.Length; ++i) { - TrafficLightSimulations[i] = new TrafficLightSimulation((ushort)i); - } - } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"Traffic light simulations:"); - for (int i = 0; i < TrafficLightSimulations.Length; ++i) { - if (! TrafficLightSimulations[i].HasSimulation()) { - continue; - } - Log._Debug($"Simulation {i}: {TrafficLightSimulations[i]}"); - } - } - - public void GetTrafficLightState( + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Enums; + using API.TrafficLight; + using CSUtil.Commons; + using State; + using Traffic; + using TrafficLight; + using TrafficLight.Data; + using TrafficLight.Impl; + using static RoadBaseAI; + + public class TrafficLightSimulationManager : AbstractGeometryObservingManager, ICustomDataManager>, ITrafficLightSimulationManager { + public static readonly TrafficLightSimulationManager Instance = new TrafficLightSimulationManager(); + public const int SIM_MOD = 64; + + /// + /// For each node id: traffic light simulation assigned to the node + /// + public TrafficLightSimulation[] TrafficLightSimulations { get; private set; } = null; + //public Dictionary TrafficLightSimulations = new Dictionary(); + + private TrafficLightSimulationManager() { + TrafficLightSimulations = new TrafficLightSimulation[NetManager.MAX_NODE_COUNT]; + for (int i = 0; i < TrafficLightSimulations.Length; ++i) { + TrafficLightSimulations[i] = new TrafficLightSimulation((ushort)i); + } + } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"Traffic light simulations:"); + for (int i = 0; i < TrafficLightSimulations.Length; ++i) { + if (! TrafficLightSimulations[i].HasSimulation()) { + continue; + } + Log._Debug($"Simulation {i}: {TrafficLightSimulations[i]}"); + } + } + + public void GetTrafficLightState( #if DEBUG - ushort vehicleId, ref Vehicle vehicleData, + ushort vehicleId, ref Vehicle vehicleData, #endif - ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, ref NetSegment segmentData, uint frame, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState) { + ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, ref NetSegment segmentData, uint frame, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState) { - bool callStockMethod = true; + bool callStockMethod = true; #if BENCHMARK using (var bm = new Benchmark(null, "callStockMethod")) { #endif - callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning(); + callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning(); #if BENCHMARK } #endif - if (callStockMethod) { - RoadBaseAI.GetTrafficLightState(nodeId, ref segmentData, frame, out vehicleLightState, out pedestrianLightState); - } else { + if (callStockMethod) { + RoadBaseAI.GetTrafficLightState(nodeId, ref segmentData, frame, out vehicleLightState, out pedestrianLightState); + } else { #if BENCHMARK using (var bm = new Benchmark(null, "GetCustomTrafficLightState")) { #endif - GetCustomTrafficLightState( + GetCustomTrafficLightState( #if DEBUG - vehicleId, ref vehicleData, + vehicleId, ref vehicleData, #endif - nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); + nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); #if BENCHMARK } #endif - } - } + } + } - public void GetTrafficLightState( + public void GetTrafficLightState( #if DEBUG - ushort vehicleId, ref Vehicle vehicleData, + ushort vehicleId, ref Vehicle vehicleData, #endif - ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, ref NetSegment segmentData, uint frame, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState, out bool vehicles, out bool pedestrians) { + ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, ref NetSegment segmentData, uint frame, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState, out bool vehicles, out bool pedestrians) { - bool callStockMethod = true; + bool callStockMethod = true; #if BENCHMARK using (var bm = new Benchmark(null, "callStockMethod")) { #endif - callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning(); + callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning(); #if BENCHMARK } #endif - if (callStockMethod) { - RoadBaseAI.GetTrafficLightState(nodeId, ref segmentData, frame, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); - } else { + if (callStockMethod) { + RoadBaseAI.GetTrafficLightState(nodeId, ref segmentData, frame, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); + } else { #if BENCHMARK using (var bm = new Benchmark(null, "GetCustomTrafficLightState")) { #endif - GetCustomTrafficLightState( + GetCustomTrafficLightState( #if DEBUG - vehicleId, ref vehicleData, + vehicleId, ref vehicleData, #endif - nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); + nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); #if BENCHMARK } #endif - vehicles = false; - pedestrians = false; - } - } + vehicles = false; + pedestrians = false; + } + } - // TODO this should be optimized - protected void GetCustomTrafficLightState( + // TODO this should be optimized + protected void GetCustomTrafficLightState( #if DEBUG - ushort vehicleId, ref Vehicle vehicleData, -#endif - ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState, ref TrafficLightSimulation nodeSim) { - - // get responsible traffic light - //Log._Debug($"GetTrafficLightState: Getting custom light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}."); - //SegmentGeometry geometry = SegmentGeometry.Get(fromSegmentId); - //if (geometry == null) { - // Log.Error($"GetTrafficLightState: No geometry information @ node {nodeId}, segment {fromSegmentId}."); - // vehicleLightState = TrafficLightState.Green; - // pedestrianLightState = TrafficLightState.Green; - // return; - //} - - // determine node position at `fromSegment` (start/end) - //bool isStartNode = geometry.StartNodeId == nodeId; - bool? isStartNode = Services.NetService.IsStartNode(fromSegmentId, nodeId); - if (isStartNode == null) { - Log.Error($"GetTrafficLightState: Invalid node {nodeId} for segment {fromSegmentId}."); - vehicleLightState = TrafficLightState.Green; - pedestrianLightState = TrafficLightState.Green; - return; - } - - ICustomSegmentLights lights = CustomSegmentLightsManager.Instance.GetSegmentLights(fromSegmentId, (bool)isStartNode, false); - - if (lights != null) { - // get traffic lights state for pedestrians - pedestrianLightState = (lights.PedestrianLightState != null) ? (RoadBaseAI.TrafficLightState)lights.PedestrianLightState : RoadBaseAI.TrafficLightState.Green; - } else { - pedestrianLightState = TrafficLightState.Green; - Log._Debug($"GetTrafficLightState: No pedestrian light @ node {nodeId}, segment {fromSegmentId} found."); - } - - ICustomSegmentLight light = lights == null ? null : lights.GetCustomLight(fromLaneIndex); - if (lights == null || light == null) { - //Log.Warning($"GetTrafficLightState: No custom light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex} found. lights null? {lights == null} light null? {light == null}"); - vehicleLightState = RoadBaseAI.TrafficLightState.Green; - return; - } - - // get traffic light state from responsible traffic light - vehicleLightState = light.GetLightState(toSegmentId); + ushort vehicleId, ref Vehicle vehicleData, +#endif + ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState, ref TrafficLightSimulation nodeSim) { + + // get responsible traffic light + //Log._Debug($"GetTrafficLightState: Getting custom light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}."); + //SegmentGeometry geometry = SegmentGeometry.Get(fromSegmentId); + //if (geometry == null) { + // Log.Error($"GetTrafficLightState: No geometry information @ node {nodeId}, segment {fromSegmentId}."); + // vehicleLightState = TrafficLightState.Green; + // pedestrianLightState = TrafficLightState.Green; + // return; + //} + + // determine node position at `fromSegment` (start/end) + //bool isStartNode = geometry.StartNodeId == nodeId; + bool? isStartNode = Services.NetService.IsStartNode(fromSegmentId, nodeId); + if (isStartNode == null) { + Log.Error($"GetTrafficLightState: Invalid node {nodeId} for segment {fromSegmentId}."); + vehicleLightState = TrafficLightState.Green; + pedestrianLightState = TrafficLightState.Green; + return; + } + + ICustomSegmentLights lights = CustomSegmentLightsManager.Instance.GetSegmentLights(fromSegmentId, (bool)isStartNode, false); + + if (lights != null) { + // get traffic lights state for pedestrians + pedestrianLightState = (lights.PedestrianLightState != null) ? (RoadBaseAI.TrafficLightState)lights.PedestrianLightState : RoadBaseAI.TrafficLightState.Green; + } else { + pedestrianLightState = TrafficLightState.Green; + Log._Debug($"GetTrafficLightState: No pedestrian light @ node {nodeId}, segment {fromSegmentId} found."); + } + + ICustomSegmentLight light = lights == null ? null : lights.GetCustomLight(fromLaneIndex); + if (lights == null || light == null) { + //Log.Warning($"GetTrafficLightState: No custom light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex} found. lights null? {lights == null} light null? {light == null}"); + vehicleLightState = RoadBaseAI.TrafficLightState.Green; + return; + } + + // get traffic light state from responsible traffic light + vehicleLightState = light.GetLightState(toSegmentId); #if DEBUG - //Log._Debug($"GetTrafficLightState: Getting light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}. vehicleLightState={vehicleLightState}, pedestrianLightState={pedestrianLightState}"); -#endif - } - - public void SetVisualState(ushort nodeId, ref NetSegment segmentData, uint frame, RoadBaseAI.TrafficLightState vehicleLightState, RoadBaseAI.TrafficLightState pedestrianLightState, bool vehicles, bool pedestrians) { - // stock code from RoadBaseAI.SetTrafficLightState - - int num = (int)pedestrianLightState << 2 | (int)vehicleLightState; - if (segmentData.m_startNode == nodeId) { - if ((frame >> 8 & 1u) == 0u) { - segmentData.m_trafficLightState0 = (byte)((int)(segmentData.m_trafficLightState0 & 240) | num); - } else { - segmentData.m_trafficLightState1 = (byte)((int)(segmentData.m_trafficLightState1 & 240) | num); - } - if (vehicles) { - segmentData.m_flags |= NetSegment.Flags.TrafficStart; - } else { - segmentData.m_flags &= ~NetSegment.Flags.TrafficStart; - } - if (pedestrians) { - segmentData.m_flags |= NetSegment.Flags.CrossingStart; - } else { - segmentData.m_flags &= ~NetSegment.Flags.CrossingStart; - } - } else { - if ((frame >> 8 & 1u) == 0u) { - segmentData.m_trafficLightState0 = (byte)((int)(segmentData.m_trafficLightState0 & 15) | num << 4); - } else { - segmentData.m_trafficLightState1 = (byte)((int)(segmentData.m_trafficLightState1 & 15) | num << 4); - } - if (vehicles) { - segmentData.m_flags |= NetSegment.Flags.TrafficEnd; - } else { - segmentData.m_flags &= ~NetSegment.Flags.TrafficEnd; - } - if (pedestrians) { - segmentData.m_flags |= NetSegment.Flags.CrossingEnd; - } else { - segmentData.m_flags &= ~NetSegment.Flags.CrossingEnd; - } - } - } - - public void SimulationStep() { - int frame = (int)(Services.SimulationService.CurrentFrameIndex & (SIM_MOD - 1)); - int minIndex = frame * (NetManager.MAX_NODE_COUNT / SIM_MOD); - int maxIndex = (frame + 1) * (NetManager.MAX_NODE_COUNT / SIM_MOD) - 1; - - ushort failedNodeId = 0; - try { - for (int nodeId = minIndex; nodeId <= maxIndex; ++nodeId) { - failedNodeId = (ushort)nodeId; - TrafficLightSimulations[nodeId].SimulationStep(); - } - failedNodeId = 0; - } catch (Exception ex) { - Log.Error($"Error occured while simulating traffic light @ node {failedNodeId}: {ex.ToString()}"); - if (failedNodeId != 0) { - RemoveNodeFromSimulation((ushort)failedNodeId); - } - } - } - - /// - /// Adds a manual traffic light simulation to the node with the given id - /// - /// - public bool SetUpManualTrafficLight(ushort nodeId) { - return SetUpManualTrafficLight(ref TrafficLightSimulations[nodeId]); - } - - /// - /// Adds a timed traffic light simulation to the node with the given id - /// - /// - public bool SetUpTimedTrafficLight(ushort nodeId, IList nodeGroup) { // TODO improve signature - if (! SetUpTimedTrafficLight(ref TrafficLightSimulations[nodeId], nodeGroup)) { - return false; - } - - return true; - } - - /// - /// Destroys the traffic light and removes it - /// - /// - /// - public void RemoveNodeFromSimulation(ushort nodeId, bool destroyGroup, bool removeTrafficLight) { + //Log._Debug($"GetTrafficLightState: Getting light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}. vehicleLightState={vehicleLightState}, pedestrianLightState={pedestrianLightState}"); +#endif + } + + public void SetVisualState(ushort nodeId, ref NetSegment segmentData, uint frame, RoadBaseAI.TrafficLightState vehicleLightState, RoadBaseAI.TrafficLightState pedestrianLightState, bool vehicles, bool pedestrians) { + // stock code from RoadBaseAI.SetTrafficLightState + + int num = (int)pedestrianLightState << 2 | (int)vehicleLightState; + if (segmentData.m_startNode == nodeId) { + if ((frame >> 8 & 1u) == 0u) { + segmentData.m_trafficLightState0 = (byte)((int)(segmentData.m_trafficLightState0 & 240) | num); + } else { + segmentData.m_trafficLightState1 = (byte)((int)(segmentData.m_trafficLightState1 & 240) | num); + } + if (vehicles) { + segmentData.m_flags |= NetSegment.Flags.TrafficStart; + } else { + segmentData.m_flags &= ~NetSegment.Flags.TrafficStart; + } + if (pedestrians) { + segmentData.m_flags |= NetSegment.Flags.CrossingStart; + } else { + segmentData.m_flags &= ~NetSegment.Flags.CrossingStart; + } + } else { + if ((frame >> 8 & 1u) == 0u) { + segmentData.m_trafficLightState0 = (byte)((int)(segmentData.m_trafficLightState0 & 15) | num << 4); + } else { + segmentData.m_trafficLightState1 = (byte)((int)(segmentData.m_trafficLightState1 & 15) | num << 4); + } + if (vehicles) { + segmentData.m_flags |= NetSegment.Flags.TrafficEnd; + } else { + segmentData.m_flags &= ~NetSegment.Flags.TrafficEnd; + } + if (pedestrians) { + segmentData.m_flags |= NetSegment.Flags.CrossingEnd; + } else { + segmentData.m_flags &= ~NetSegment.Flags.CrossingEnd; + } + } + } + + public void SimulationStep() { + int frame = (int)(Services.SimulationService.CurrentFrameIndex & (SIM_MOD - 1)); + int minIndex = frame * (NetManager.MAX_NODE_COUNT / SIM_MOD); + int maxIndex = (frame + 1) * (NetManager.MAX_NODE_COUNT / SIM_MOD) - 1; + + ushort failedNodeId = 0; + try { + for (int nodeId = minIndex; nodeId <= maxIndex; ++nodeId) { + failedNodeId = (ushort)nodeId; + TrafficLightSimulations[nodeId].SimulationStep(); + } + failedNodeId = 0; + } catch (Exception ex) { + Log.Error($"Error occured while simulating traffic light @ node {failedNodeId}: {ex.ToString()}"); + if (failedNodeId != 0) { + RemoveNodeFromSimulation((ushort)failedNodeId); + } + } + } + + /// + /// Adds a manual traffic light simulation to the node with the given id + /// + /// + public bool SetUpManualTrafficLight(ushort nodeId) { + return SetUpManualTrafficLight(ref TrafficLightSimulations[nodeId]); + } + + /// + /// Adds a timed traffic light simulation to the node with the given id + /// + /// + public bool SetUpTimedTrafficLight(ushort nodeId, IList nodeGroup) { // TODO improve signature + if (! SetUpTimedTrafficLight(ref TrafficLightSimulations[nodeId], nodeGroup)) { + return false; + } + + return true; + } + + /// + /// Destroys the traffic light and removes it + /// + /// + /// + public void RemoveNodeFromSimulation(ushort nodeId, bool destroyGroup, bool removeTrafficLight) { #if DEBUG - Log._Debug($"TrafficLightSimulationManager.RemoveNodeFromSimulation({nodeId}, {destroyGroup}, {removeTrafficLight}) called."); -#endif - - if (! TrafficLightSimulations[nodeId].HasSimulation()) { - return; - } - TrafficLightManager tlm = TrafficLightManager.Instance; - - if (TrafficLightSimulations[nodeId].IsTimedLight()) { - // remove/destroy all timed traffic lights in group - List oldNodeGroup = new List(TrafficLightSimulations[nodeId].timedLight.NodeGroup); - foreach (var timedNodeId in oldNodeGroup) { - if (!TrafficLightSimulations[timedNodeId].HasSimulation()) { - continue; - } - - if (destroyGroup || timedNodeId == nodeId) { - //Log._Debug($"Slave: Removing simulation @ node {timedNodeId}"); - //TrafficLightSimulations[timedNodeId].Destroy(); - RemoveNodeFromSimulation(timedNodeId); - if (removeTrafficLight) { - Constants.ServiceFactory.NetService.ProcessNode(timedNodeId, delegate (ushort nId, ref NetNode node) { - tlm.RemoveTrafficLight(timedNodeId, ref node); - return true; - }); - } - } else { - if (TrafficLightSimulations[nodeId].IsTimedLight()) { - TrafficLightSimulations[timedNodeId].timedLight.RemoveNodeFromGroup(nodeId); - } - } - } - } - - //Flags.setNodeTrafficLight(nodeId, false); - //sim.DestroyTimedTrafficLight(); - //TrafficLightSimulations[nodeId].DestroyManualTrafficLight(); - RemoveNodeFromSimulation(nodeId); - if (removeTrafficLight) { - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - tlm.RemoveTrafficLight(nodeId, ref node); - return true; - }); - } - } - - public bool HasSimulation(ushort nodeId) { - return TrafficLightSimulations[nodeId].HasSimulation(); - } - - public bool HasManualSimulation(ushort nodeId) { - return TrafficLightSimulations[nodeId].IsManualLight(); - } - - public bool HasTimedSimulation(ushort nodeId) { - return TrafficLightSimulations[nodeId].IsTimedLight(); - } - - public bool HasActiveTimedSimulation(ushort nodeId) { - return TrafficLightSimulations[nodeId].IsTimedLightRunning(); - } - - public bool HasActiveSimulation(ushort nodeId) { - return TrafficLightSimulations[nodeId].IsSimulationRunning(); - } - - private void RemoveNodeFromSimulation(ushort nodeId) { + Log._Debug($"TrafficLightSimulationManager.RemoveNodeFromSimulation({nodeId}, {destroyGroup}, {removeTrafficLight}) called."); +#endif + + if (! TrafficLightSimulations[nodeId].HasSimulation()) { + return; + } + TrafficLightManager tlm = TrafficLightManager.Instance; + + if (TrafficLightSimulations[nodeId].IsTimedLight()) { + // remove/destroy all timed traffic lights in group + List oldNodeGroup = new List(TrafficLightSimulations[nodeId].timedLight.NodeGroup); + foreach (var timedNodeId in oldNodeGroup) { + if (!TrafficLightSimulations[timedNodeId].HasSimulation()) { + continue; + } + + if (destroyGroup || timedNodeId == nodeId) { + //Log._Debug($"Slave: Removing simulation @ node {timedNodeId}"); + //TrafficLightSimulations[timedNodeId].Destroy(); + RemoveNodeFromSimulation(timedNodeId); + if (removeTrafficLight) { + Constants.ServiceFactory.NetService.ProcessNode(timedNodeId, delegate (ushort nId, ref NetNode node) { + tlm.RemoveTrafficLight(timedNodeId, ref node); + return true; + }); + } + } else { + if (TrafficLightSimulations[nodeId].IsTimedLight()) { + TrafficLightSimulations[timedNodeId].timedLight.RemoveNodeFromGroup(nodeId); + } + } + } + } + + //Flags.setNodeTrafficLight(nodeId, false); + //sim.DestroyTimedTrafficLight(); + //TrafficLightSimulations[nodeId].DestroyManualTrafficLight(); + RemoveNodeFromSimulation(nodeId); + if (removeTrafficLight) { + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + tlm.RemoveTrafficLight(nodeId, ref node); + return true; + }); + } + } + + public bool HasSimulation(ushort nodeId) { + return TrafficLightSimulations[nodeId].HasSimulation(); + } + + public bool HasManualSimulation(ushort nodeId) { + return TrafficLightSimulations[nodeId].IsManualLight(); + } + + public bool HasTimedSimulation(ushort nodeId) { + return TrafficLightSimulations[nodeId].IsTimedLight(); + } + + public bool HasActiveTimedSimulation(ushort nodeId) { + return TrafficLightSimulations[nodeId].IsTimedLightRunning(); + } + + public bool HasActiveSimulation(ushort nodeId) { + return TrafficLightSimulations[nodeId].IsSimulationRunning(); + } + + private void RemoveNodeFromSimulation(ushort nodeId) { #if DEBUG - Log._Debug($"TrafficLightSimulationManager.RemoveNodeFromSimulation({nodeId}) called."); -#endif - - Destroy(ref TrafficLightSimulations[nodeId]); - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { - Destroy(ref TrafficLightSimulations[nodeId]); - } - } - - public bool SetUpManualTrafficLight(ref TrafficLightSimulation sim) { - if (sim.IsTimedLight()) { - return false; - } - - Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { - Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); - return true; - }); - - Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); - sim.type = TrafficLightSimulationType.Manual; - return true; - } - - public bool DestroyManualTrafficLight(ref TrafficLightSimulation sim) { - if (sim.IsTimedLight()) { - return false; - } - if (!sim.IsManualLight()) { - return false; - } - - sim.type = TrafficLightSimulationType.None; - Constants.ManagerFactory.CustomSegmentLightsManager.RemoveNodeLights(sim.nodeId); - return true; - } - - public bool SetUpTimedTrafficLight(ref TrafficLightSimulation sim, IList nodeGroup) { - if (sim.IsManualLight()) { - DestroyManualTrafficLight(ref sim); - } - - if (sim.IsTimedLight()) { - return false; - } - - Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { - Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); - return true; - }); - - Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); - sim.timedLight = new TimedTrafficLights(sim.nodeId, nodeGroup); - sim.type = TrafficLightSimulationType.Timed; - return true; - } - - public bool DestroyTimedTrafficLight(ref TrafficLightSimulation sim) { - if (!sim.IsTimedLight()) { - return false; - } - - sim.type = TrafficLightSimulationType.None; - var timedLight = sim.timedLight; - sim.timedLight = null; - - if (timedLight != null) { - timedLight.Destroy(); - } - return true; - } - - public void Destroy(ref TrafficLightSimulation sim) { - DestroyTimedTrafficLight(ref sim); - DestroyManualTrafficLight(ref sim); - } - - protected override void HandleInvalidNode(ushort nodeId, ref NetNode node) { - RemoveNodeFromSimulation(nodeId, false, true); - } - - protected override void HandleValidNode(ushort nodeId, ref NetNode node) { + Log._Debug($"TrafficLightSimulationManager.RemoveNodeFromSimulation({nodeId}) called."); +#endif + + Destroy(ref TrafficLightSimulations[nodeId]); + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { + Destroy(ref TrafficLightSimulations[nodeId]); + } + } + + public bool SetUpManualTrafficLight(ref TrafficLightSimulation sim) { + if (sim.IsTimedLight()) { + return false; + } + + Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { + Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); + return true; + }); + + Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); + sim.type = TrafficLightSimulationType.Manual; + return true; + } + + public bool DestroyManualTrafficLight(ref TrafficLightSimulation sim) { + if (sim.IsTimedLight()) { + return false; + } + if (!sim.IsManualLight()) { + return false; + } + + sim.type = TrafficLightSimulationType.None; + Constants.ManagerFactory.CustomSegmentLightsManager.RemoveNodeLights(sim.nodeId); + return true; + } + + public bool SetUpTimedTrafficLight(ref TrafficLightSimulation sim, IList nodeGroup) { + if (sim.IsManualLight()) { + DestroyManualTrafficLight(ref sim); + } + + if (sim.IsTimedLight()) { + return false; + } + + Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { + Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); + return true; + }); + + Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); + sim.timedLight = new TimedTrafficLights(sim.nodeId, nodeGroup); + sim.type = TrafficLightSimulationType.Timed; + return true; + } + + public bool DestroyTimedTrafficLight(ref TrafficLightSimulation sim) { + if (!sim.IsTimedLight()) { + return false; + } + + sim.type = TrafficLightSimulationType.None; + var timedLight = sim.timedLight; + sim.timedLight = null; + + if (timedLight != null) { + timedLight.Destroy(); + } + return true; + } + + public void Destroy(ref TrafficLightSimulation sim) { + DestroyTimedTrafficLight(ref sim); + DestroyManualTrafficLight(ref sim); + } + + protected override void HandleInvalidNode(ushort nodeId, ref NetNode node) { + RemoveNodeFromSimulation(nodeId, false, true); + } + + protected override void HandleValidNode(ushort nodeId, ref NetNode node) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == nodeId); + bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == nodeId); #endif - if (!TrafficLightSimulations[nodeId].HasSimulation()) { + if (!TrafficLightSimulations[nodeId].HasSimulation()) { #if DEBUG - if (debug) - Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node is not controlled by a custom traffic light simulation."); + if (debug) + Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node is not controlled by a custom traffic light simulation."); #endif - return; - } + return; + } - if (! Flags.mayHaveTrafficLight(nodeId)) { + if (! Flags.mayHaveTrafficLight(nodeId)) { #if DEBUG - if (debug) - Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node must not have a traffic light: Removing traffic light simulation."); + if (debug) + Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node must not have a traffic light: Removing traffic light simulation."); #endif - RemoveNodeFromSimulation(nodeId, false, true); - return; - } + RemoveNodeFromSimulation(nodeId, false, true); + return; + } - for (int i = 0; i < 8; ++i) { - ushort segmentId = node.GetSegment(i); - if (segmentId == 0) { - continue; - } + for (int i = 0; i < 8; ++i) { + ushort segmentId = node.GetSegment(i); + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, nodeId); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, nodeId); #if DEBUG - if (debug) - Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Adding live traffic lights to segment {segmentId}"); -#endif - // housekeep timed light - CustomSegmentLightsManager.Instance.GetSegmentLights(segmentId, startNode).Housekeeping(true, true); - } - - // ensure there is a physical traffic light - Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nodeId, ref node); - - TrafficLightSimulations[nodeId].Update(); - } - - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading {data.Count} timed traffic lights (new method)"); - - TrafficLightManager tlm = TrafficLightManager.Instance; - - HashSet nodesWithSimulation = new HashSet(); - foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { - nodesWithSimulation.Add(cnfTimedLights.nodeId); - } - - Dictionary masterNodeIdBySlaveNodeId = new Dictionary(); - Dictionary> nodeGroupByMasterNodeId = new Dictionary>(); - foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { - try { - // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed - List currentNodeGroup = cnfTimedLights.nodeGroup.Distinct().ToList(); // enforce uniqueness of node ids - if (!currentNodeGroup.Contains(cnfTimedLights.nodeId)) - currentNodeGroup.Add(cnfTimedLights.nodeId); - // remove any nodes that are not configured to have a simulation - currentNodeGroup = new List(currentNodeGroup.Intersect(nodesWithSimulation)); - - // remove invalid nodes from the group; find if any of the nodes in the group is already a master node - ushort masterNodeId = 0; - int foundMasterNodes = 0; - for (int i = 0; i < currentNodeGroup.Count;) { - ushort nodeId = currentNodeGroup[i]; - if (!Services.NetService.IsNodeValid(currentNodeGroup[i])) { - currentNodeGroup.RemoveAt(i); - continue; - } else if (nodeGroupByMasterNodeId.ContainsKey(nodeId)) { - // this is a known master node - if (foundMasterNodes > 0) { - // we already found another master node. ignore this node. - currentNodeGroup.RemoveAt(i); - continue; - } - // we found the first master node - masterNodeId = nodeId; - ++foundMasterNodes; - } - ++i; - } - - if (masterNodeId == 0) { - // no master node defined yet, set the first node as a master node - masterNodeId = currentNodeGroup[0]; - } - - // ensure the master node is the first node in the list (TimedTrafficLights depends on this at the moment...) - currentNodeGroup.Remove(masterNodeId); - currentNodeGroup.Insert(0, masterNodeId); - - // update the saved node group and master-slave info - nodeGroupByMasterNodeId[masterNodeId] = currentNodeGroup; - foreach (ushort nodeId in currentNodeGroup) { - masterNodeIdBySlaveNodeId[nodeId] = masterNodeId; - } - } catch (Exception e) { - Log.Warning($"Error building timed traffic light group for TimedNode {cnfTimedLights.nodeId} (NodeGroup: {string.Join(", ", cnfTimedLights.nodeGroup.Select(x => x.ToString()).ToArray())}): " + e.ToString()); - success = false; - } - } - - foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { - try { - if (!masterNodeIdBySlaveNodeId.ContainsKey(cnfTimedLights.nodeId)) - continue; - ushort masterNodeId = masterNodeIdBySlaveNodeId[cnfTimedLights.nodeId]; - List nodeGroup = nodeGroupByMasterNodeId[masterNodeId]; + if (debug) + Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Adding live traffic lights to segment {segmentId}"); +#endif + // housekeep timed light + CustomSegmentLightsManager.Instance.GetSegmentLights(segmentId, startNode).Housekeeping(true, true); + } + + // ensure there is a physical traffic light + Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nodeId, ref node); + + TrafficLightSimulations[nodeId].Update(); + } + + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading {data.Count} timed traffic lights (new method)"); + + TrafficLightManager tlm = TrafficLightManager.Instance; + + HashSet nodesWithSimulation = new HashSet(); + foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { + nodesWithSimulation.Add(cnfTimedLights.nodeId); + } + + Dictionary masterNodeIdBySlaveNodeId = new Dictionary(); + Dictionary> nodeGroupByMasterNodeId = new Dictionary>(); + foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { + try { + // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed + List currentNodeGroup = cnfTimedLights.nodeGroup.Distinct().ToList(); // enforce uniqueness of node ids + if (!currentNodeGroup.Contains(cnfTimedLights.nodeId)) + currentNodeGroup.Add(cnfTimedLights.nodeId); + // remove any nodes that are not configured to have a simulation + currentNodeGroup = new List(currentNodeGroup.Intersect(nodesWithSimulation)); + + // remove invalid nodes from the group; find if any of the nodes in the group is already a master node + ushort masterNodeId = 0; + int foundMasterNodes = 0; + for (int i = 0; i < currentNodeGroup.Count;) { + ushort nodeId = currentNodeGroup[i]; + if (!Services.NetService.IsNodeValid(currentNodeGroup[i])) { + currentNodeGroup.RemoveAt(i); + continue; + } else if (nodeGroupByMasterNodeId.ContainsKey(nodeId)) { + // this is a known master node + if (foundMasterNodes > 0) { + // we already found another master node. ignore this node. + currentNodeGroup.RemoveAt(i); + continue; + } + // we found the first master node + masterNodeId = nodeId; + ++foundMasterNodes; + } + ++i; + } + + if (masterNodeId == 0) { + // no master node defined yet, set the first node as a master node + masterNodeId = currentNodeGroup[0]; + } + + // ensure the master node is the first node in the list (TimedTrafficLights depends on this at the moment...) + currentNodeGroup.Remove(masterNodeId); + currentNodeGroup.Insert(0, masterNodeId); + + // update the saved node group and master-slave info + nodeGroupByMasterNodeId[masterNodeId] = currentNodeGroup; + foreach (ushort nodeId in currentNodeGroup) { + masterNodeIdBySlaveNodeId[nodeId] = masterNodeId; + } + } catch (Exception e) { + Log.Warning($"Error building timed traffic light group for TimedNode {cnfTimedLights.nodeId} (NodeGroup: {string.Join(", ", cnfTimedLights.nodeGroup.Select(x => x.ToString()).ToArray())}): " + e.ToString()); + success = false; + } + } + + foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { + try { + if (!masterNodeIdBySlaveNodeId.ContainsKey(cnfTimedLights.nodeId)) + continue; + ushort masterNodeId = masterNodeIdBySlaveNodeId[cnfTimedLights.nodeId]; + List nodeGroup = nodeGroupByMasterNodeId[masterNodeId]; #if DEBUGLOAD Log._Debug($"Adding timed light at node {cnfTimedLights.nodeId}. NodeGroup: {string.Join(", ", nodeGroup.Select(x => x.ToString()).ToArray())}"); #endif - SetUpTimedTrafficLight(cnfTimedLights.nodeId, nodeGroup); + SetUpTimedTrafficLight(cnfTimedLights.nodeId, nodeGroup); - int j = 0; - foreach (Configuration.TimedTrafficLightsStep cnfTimedStep in cnfTimedLights.timedSteps) { + int j = 0; + foreach (Configuration.TimedTrafficLightsStep cnfTimedStep in cnfTimedLights.timedSteps) { #if DEBUGLOAD Log._Debug($"Loading timed step {j} at node {cnfTimedLights.nodeId}"); #endif - ITimedTrafficLightsStep step = TrafficLightSimulations[cnfTimedLights.nodeId].timedLight.AddStep(cnfTimedStep.minTime, cnfTimedStep.maxTime, (StepChangeMetric)cnfTimedStep.changeMetric, cnfTimedStep.waitFlowBalance); + ITimedTrafficLightsStep step = TrafficLightSimulations[cnfTimedLights.nodeId].timedLight.AddStep(cnfTimedStep.minTime, cnfTimedStep.maxTime, (StepChangeMetric)cnfTimedStep.changeMetric, cnfTimedStep.waitFlowBalance); - foreach (KeyValuePair e in cnfTimedStep.segmentLights) { - if (!Services.NetService.IsSegmentValid(e.Key)) - continue; - e.Value.nodeId = cnfTimedLights.nodeId; + foreach (KeyValuePair e in cnfTimedStep.segmentLights) { + if (!Services.NetService.IsSegmentValid(e.Key)) + continue; + e.Value.nodeId = cnfTimedLights.nodeId; #if DEBUGLOAD Log._Debug($"Loading timed step {j}, segment {e.Key} at node {cnfTimedLights.nodeId}"); #endif - ICustomSegmentLights lights = null; - if (!step.CustomSegmentLights.TryGetValue(e.Key, out lights)) { + ICustomSegmentLights lights = null; + if (!step.CustomSegmentLights.TryGetValue(e.Key, out lights)) { #if DEBUGLOAD Log._Debug($"No segment lights found at timed step {j} for segment {e.Key}, node {cnfTimedLights.nodeId}"); #endif - continue; - } - Configuration.CustomSegmentLights cnfLights = e.Value; + continue; + } + Configuration.CustomSegmentLights cnfLights = e.Value; #if DEBUGLOAD Log._Debug($"Loading pedestrian light @ seg. {e.Key}, step {j}: {cnfLights.pedestrianLightState} {cnfLights.manualPedestrianMode}"); #endif - lights.ManualPedestrianMode = cnfLights.manualPedestrianMode; - lights.PedestrianLightState = cnfLights.pedestrianLightState; + lights.ManualPedestrianMode = cnfLights.manualPedestrianMode; + lights.PedestrianLightState = cnfLights.pedestrianLightState; - bool first = true; // v1.10.2 transitional code - foreach (KeyValuePair e2 in cnfLights.customLights) { + bool first = true; // v1.10.2 transitional code + foreach (var e2 in cnfLights.customLights) { #if DEBUGLOAD Log._Debug($"Loading timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); #endif - ICustomSegmentLight light = null; - if (!lights.CustomLights.TryGetValue(e2.Key, out light)) { + ICustomSegmentLight light = null; + if (!lights.CustomLights.TryGetValue(LegacyExtVehicleType.ToNew(e2.Key), out light)) { #if DEBUGLOAD Log._Debug($"No segment light found for timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); #endif - // v1.10.2 transitional code START - if (first) { - first = false; - if (!lights.CustomLights.TryGetValue(CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE, out light)) { + // v1.10.2 transitional code START + if (first) { + first = false; + if (!lights.CustomLights.TryGetValue(CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE, out light)) { #if DEBUGLOAD Log._Debug($"No segment light found for timed step {j}, segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} at node {cnfTimedLights.nodeId}"); #endif - continue; - } - } else { - // v1.10.2 transitional code END - continue; - // v1.10.2 transitional code START - } - // v1.10.2 transitional code END - } - Configuration.CustomSegmentLight cnfLight = e2.Value; - - light.InternalCurrentMode = (LightMode)cnfLight.currentMode; // TODO improve & remove - light.SetStates(cnfLight.mainLight, cnfLight.leftLight, cnfLight.rightLight, false); - } - } - ++j; - } - } catch (Exception e) { - // ignore, as it's probably corrupt save data. it'll be culled on next save - Log.Warning("Error loading data from TimedNode (new method): " + e.ToString()); - success = false; - } - } - - foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { - try { - var timedNode = TrafficLightSimulations[cnfTimedLights.nodeId].timedLight; - - timedNode.Housekeeping(); - if (cnfTimedLights.started) { - timedNode.Start(cnfTimedLights.currentStep); - } - } catch (Exception e) { - Log.Warning($"Error starting timed light @ {cnfTimedLights.nodeId}: " + e.ToString()); - success = false; - } - } - - return success; - } - - public List SaveData(ref bool success) { - List ret = new List(); - for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { - try { - if (! TrafficLightSimulations[nodeId].IsTimedLight()) { - continue; - } + continue; + } + } else { + // v1.10.2 transitional code END + continue; + // v1.10.2 transitional code START + } + // v1.10.2 transitional code END + } + Configuration.CustomSegmentLight cnfLight = e2.Value; + + light.InternalCurrentMode = (LightMode)cnfLight.currentMode; // TODO improve & remove + light.SetStates(cnfLight.mainLight, cnfLight.leftLight, cnfLight.rightLight, false); + } + } + ++j; + } + } catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Warning("Error loading data from TimedNode (new method): " + e.ToString()); + success = false; + } + } + + foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { + try { + var timedNode = TrafficLightSimulations[cnfTimedLights.nodeId].timedLight; + + timedNode.Housekeeping(); + if (cnfTimedLights.started) { + timedNode.Start(cnfTimedLights.currentStep); + } + } catch (Exception e) { + Log.Warning($"Error starting timed light @ {cnfTimedLights.nodeId}: " + e.ToString()); + success = false; + } + } + + return success; + } + + public List SaveData(ref bool success) { + List ret = new List(); + for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { + try { + if (! TrafficLightSimulations[nodeId].IsTimedLight()) { + continue; + } #if DEBUGSAVE Log._Debug($"Going to save timed light at node {nodeId}."); #endif - var timedNode = TrafficLightSimulations[nodeId].timedLight; - timedNode.OnGeometryUpdate(); + var timedNode = TrafficLightSimulations[nodeId].timedLight; + timedNode.OnGeometryUpdate(); - Configuration.TimedTrafficLights cnfTimedLights = new Configuration.TimedTrafficLights(); - ret.Add(cnfTimedLights); + Configuration.TimedTrafficLights cnfTimedLights = new Configuration.TimedTrafficLights(); + ret.Add(cnfTimedLights); - cnfTimedLights.nodeId = timedNode.NodeId; - cnfTimedLights.nodeGroup = new List(timedNode.NodeGroup); - cnfTimedLights.started = timedNode.IsStarted(); - int stepIndex = timedNode.CurrentStep; - if (timedNode.IsStarted() && timedNode.GetStep(timedNode.CurrentStep).IsInEndTransition()) { - // if in end transition save the next step - stepIndex = (stepIndex + 1) % timedNode.NumSteps(); - } - cnfTimedLights.currentStep = stepIndex; - cnfTimedLights.timedSteps = new List(); + cnfTimedLights.nodeId = timedNode.NodeId; + cnfTimedLights.nodeGroup = new List(timedNode.NodeGroup); + cnfTimedLights.started = timedNode.IsStarted(); + int stepIndex = timedNode.CurrentStep; + if (timedNode.IsStarted() && timedNode.GetStep(timedNode.CurrentStep).IsInEndTransition()) { + // if in end transition save the next step + stepIndex = (stepIndex + 1) % timedNode.NumSteps(); + } + cnfTimedLights.currentStep = stepIndex; + cnfTimedLights.timedSteps = new List(); - for (var j = 0; j < timedNode.NumSteps(); j++) { + for (var j = 0; j < timedNode.NumSteps(); j++) { #if DEBUGSAVE Log._Debug($"Saving timed light step {j} at node {nodeId}."); #endif - ITimedTrafficLightsStep timedStep = timedNode.GetStep(j); - Configuration.TimedTrafficLightsStep cnfTimedStep = new Configuration.TimedTrafficLightsStep(); - cnfTimedLights.timedSteps.Add(cnfTimedStep); + ITimedTrafficLightsStep timedStep = timedNode.GetStep(j); + Configuration.TimedTrafficLightsStep cnfTimedStep = new Configuration.TimedTrafficLightsStep(); + cnfTimedLights.timedSteps.Add(cnfTimedStep); - cnfTimedStep.minTime = timedStep.MinTime; - cnfTimedStep.maxTime = timedStep.MaxTime; - cnfTimedStep.changeMetric = (int)timedStep.ChangeMetric; - cnfTimedStep.waitFlowBalance = timedStep.WaitFlowBalance; - cnfTimedStep.segmentLights = new Dictionary(); - foreach (KeyValuePair e in timedStep.CustomSegmentLights) { + cnfTimedStep.minTime = timedStep.MinTime; + cnfTimedStep.maxTime = timedStep.MaxTime; + cnfTimedStep.changeMetric = (int)timedStep.ChangeMetric; + cnfTimedStep.waitFlowBalance = timedStep.WaitFlowBalance; + cnfTimedStep.segmentLights = new Dictionary(); + foreach (KeyValuePair e in timedStep.CustomSegmentLights) { #if DEBUGSAVE Log._Debug($"Saving timed light step {j}, segment {e.Key} at node {nodeId}."); #endif - ICustomSegmentLights segLights = e.Value; - Configuration.CustomSegmentLights cnfSegLights = new Configuration.CustomSegmentLights(); + ICustomSegmentLights segLights = e.Value; + Configuration.CustomSegmentLights cnfSegLights = new Configuration.CustomSegmentLights(); - ushort lightsNodeId = segLights.NodeId; - if (lightsNodeId == 0 || lightsNodeId != timedNode.NodeId) { - Log.Warning($"Inconsistency detected: Timed traffic light @ node {timedNode.NodeId} contains custom traffic lights for the invalid segment ({segLights.SegmentId}) at step {j}: nId={lightsNodeId}"); - continue; - } + ushort lightsNodeId = segLights.NodeId; + if (lightsNodeId == 0 || lightsNodeId != timedNode.NodeId) { + Log.Warning($"Inconsistency detected: Timed traffic light @ node {timedNode.NodeId} contains custom traffic lights for the invalid segment ({segLights.SegmentId}) at step {j}: nId={lightsNodeId}"); + continue; + } - cnfSegLights.nodeId = lightsNodeId; // TODO not needed - cnfSegLights.segmentId = segLights.SegmentId; // TODO not needed - cnfSegLights.customLights = new Dictionary(); - cnfSegLights.pedestrianLightState = segLights.PedestrianLightState; - cnfSegLights.manualPedestrianMode = segLights.ManualPedestrianMode; + cnfSegLights.nodeId = lightsNodeId; // TODO not needed + cnfSegLights.segmentId = segLights.SegmentId; // TODO not needed + cnfSegLights.customLights = new Dictionary(); + cnfSegLights.pedestrianLightState = segLights.PedestrianLightState; + cnfSegLights.manualPedestrianMode = segLights.ManualPedestrianMode; - cnfTimedStep.segmentLights.Add(e.Key, cnfSegLights); + cnfTimedStep.segmentLights.Add(e.Key, cnfSegLights); #if DEBUGSAVE Log._Debug($"Saving pedestrian light @ seg. {e.Key}, step {j}: {cnfSegLights.pedestrianLightState} {cnfSegLights.manualPedestrianMode}"); #endif - foreach (KeyValuePair e2 in segLights.CustomLights) { + foreach (var e2 in segLights.CustomLights) { #if DEBUGSAVE Log._Debug($"Saving timed light step {j}, segment {e.Key}, vehicleType {e2.Key} at node {nodeId}."); #endif - ICustomSegmentLight segLight = e2.Value; - Configuration.CustomSegmentLight cnfSegLight = new Configuration.CustomSegmentLight(); - cnfSegLights.customLights.Add(e2.Key, cnfSegLight); - - cnfSegLight.nodeId = lightsNodeId; // TODO not needed - cnfSegLight.segmentId = segLights.SegmentId; // TODO not needed - cnfSegLight.currentMode = (int)segLight.CurrentMode; - cnfSegLight.leftLight = segLight.LightLeft; - cnfSegLight.mainLight = segLight.LightMain; - cnfSegLight.rightLight = segLight.LightRight; - } - } - } - } catch (Exception e) { - Log.Error($"Exception occurred while saving timed traffic light @ {nodeId}: {e.ToString()}"); - success = false; - } - } - return ret; - } - } -} + ICustomSegmentLight segLight = e2.Value; + Configuration.CustomSegmentLight cnfSegLight = new Configuration.CustomSegmentLight(); + cnfSegLights.customLights.Add(LegacyExtVehicleType.ToOld(e2.Key), cnfSegLight); + + cnfSegLight.nodeId = lightsNodeId; // TODO not needed + cnfSegLight.segmentId = segLights.SegmentId; // TODO not needed + cnfSegLight.currentMode = (int)segLight.CurrentMode; + cnfSegLight.leftLight = segLight.LightLeft; + cnfSegLight.mainLight = segLight.LightMain; + cnfSegLight.rightLight = segLight.LightRight; + } + } + } + } catch (Exception e) { + Log.Error($"Exception occurred while saving timed traffic light @ {nodeId}: {e.ToString()}"); + success = false; + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs b/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs index a6d714b0a..a47e1c2bb 100644 --- a/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs @@ -1,308 +1,307 @@ -using System; -using System.Collections.Generic; -using ColossalFramework; -using TrafficManager.TrafficLight; -using TrafficManager.Custom.AI; -using UnityEngine; -using TrafficManager.State; -using System.Threading; -using TrafficManager.Util; -using TrafficManager.Traffic; -using TrafficManager.Geometry; -using CSUtil.Commons; -using TrafficManager.Geometry.Impl; -using static TrafficManager.Traffic.Data.PrioritySegment; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; - namespace TrafficManager.Manager.Impl { - public class TrafficPriorityManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager>, ITrafficPriorityManager { - public static readonly TrafficPriorityManager Instance = new TrafficPriorityManager(); - - /// - /// List of segments that are connected to roads with timed traffic lights or priority signs. Index: segment id - /// - private PrioritySegment[] PrioritySegments = null; - - private PrioritySegment[] invalidPrioritySegments; - - private TrafficPriorityManager() { - PrioritySegments = new PrioritySegment[NetManager.MAX_SEGMENT_COUNT]; - invalidPrioritySegments = new PrioritySegment[NetManager.MAX_SEGMENT_COUNT]; - } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"Priority signs:"); - for (int i = 0; i < PrioritySegments.Length; ++i) { - if (PrioritySegments[i].IsDefault()) { - continue; - } - Log._Debug($"Segment {i}: {PrioritySegments[i]}"); - } - } + using System; + using System.Collections.Generic; + using API.Traffic.Data; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Geometry; + using State; + using Traffic; + using Traffic.Data; + using TrafficLight; + using UnityEngine; + + public class TrafficPriorityManager : AbstractGeometryObservingManager, + ICustomDataManager>, ICustomDataManager>, + ITrafficPriorityManager { + public static readonly TrafficPriorityManager Instance = new TrafficPriorityManager(); + + /// + /// List of segments that are connected to roads with timed traffic lights or priority signs. Index: segment id + /// + private PrioritySegment[] PrioritySegments = null; + + private PrioritySegment[] invalidPrioritySegments; + + private TrafficPriorityManager() { + PrioritySegments = new PrioritySegment[NetManager.MAX_SEGMENT_COUNT]; + invalidPrioritySegments = new PrioritySegment[NetManager.MAX_SEGMENT_COUNT]; + } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"Priority signs:"); + for (int i = 0; i < PrioritySegments.Length; ++i) { + if (PrioritySegments[i].IsDefault()) { + continue; + } + Log._Debug($"Segment {i}: {PrioritySegments[i]}"); + } + } - protected void AddInvalidPrioritySegment(ushort segmentId, ref PrioritySegment prioritySegment) { - invalidPrioritySegments[segmentId] = prioritySegment; - } + protected void AddInvalidPrioritySegment(ushort segmentId, ref PrioritySegment prioritySegment) { + invalidPrioritySegments[segmentId] = prioritySegment; + } - public bool MayNodeHavePrioritySigns(ushort nodeId) { - SetPrioritySignUnableReason reason; - return MayNodeHavePrioritySigns(nodeId, out reason); - } + public bool MayNodeHavePrioritySigns(ushort nodeId) { + SetPrioritySignUnableReason reason; + return MayNodeHavePrioritySigns(nodeId, out reason); + } - public bool MayNodeHavePrioritySigns(ushort nodeId, out SetPrioritySignUnableReason reason) { + public bool MayNodeHavePrioritySigns(ushort nodeId, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); #endif - if (!Services.NetService.CheckNodeFlags(nodeId, NetNode.Flags.Created | NetNode.Flags.Deleted | NetNode.Flags.Junction, NetNode.Flags.Created | NetNode.Flags.Junction)) { - reason = SetPrioritySignUnableReason.NoJunction; + if (!Services.NetService.CheckNodeFlags(nodeId, NetNode.Flags.Created | NetNode.Flags.Deleted | NetNode.Flags.Junction, NetNode.Flags.Created | NetNode.Flags.Junction)) { + reason = SetPrioritySignUnableReason.NoJunction; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - if (TrafficLightSimulationManager.Instance.HasTimedSimulation(nodeId)) { - reason = SetPrioritySignUnableReason.HasTimedLight; + if (TrafficLightSimulationManager.Instance.HasTimedSimulation(nodeId)) { + reason = SetPrioritySignUnableReason.HasTimedLight; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - //Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=true"); - reason = SetPrioritySignUnableReason.None; - return true; - } + //Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=true"); + reason = SetPrioritySignUnableReason.None; + return true; + } - public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode) { - SetPrioritySignUnableReason reason; - return MaySegmentHavePrioritySign(segmentId, startNode, out reason); - } + public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode) { + SetPrioritySignUnableReason reason; + return MaySegmentHavePrioritySign(segmentId, startNode, out reason); + } - public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode, out SetPrioritySignUnableReason reason) { + public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif - if (! Services.NetService.IsSegmentValid(segmentId)) { - reason = SetPrioritySignUnableReason.InvalidSegment; + if (! Services.NetService.IsSegmentValid(segmentId)) { + reason = SetPrioritySignUnableReason.InvalidSegment; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - if (! MayNodeHavePrioritySigns(Services.NetService.GetSegmentNodeId(segmentId, startNode), out reason)) { + if (! MayNodeHavePrioritySigns(Services.NetService.GetSegmentNodeId(segmentId, startNode), out reason)) { #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - if (segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)].outgoing && segMan.ExtSegments[segmentId].oneWay) { - reason = SetPrioritySignUnableReason.NotIncoming; + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + if (segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)].outgoing && segMan.ExtSegments[segmentId].oneWay) { + reason = SetPrioritySignUnableReason.NotIncoming; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=true"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=true"); + } #endif - reason = SetPrioritySignUnableReason.None; - return true; - } + reason = SetPrioritySignUnableReason.None; + return true; + } - public bool MaySegmentHavePrioritySign(ushort segmentId) { - SetPrioritySignUnableReason reason; - return MaySegmentHavePrioritySign(segmentId, out reason); - } + public bool MaySegmentHavePrioritySign(ushort segmentId) { + SetPrioritySignUnableReason reason; + return MaySegmentHavePrioritySign(segmentId, out reason); + } - public bool MaySegmentHavePrioritySign(ushort segmentId, out SetPrioritySignUnableReason reason) { + public bool MaySegmentHavePrioritySign(ushort segmentId, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif - if (!Services.NetService.IsSegmentValid(segmentId)) { - reason = SetPrioritySignUnableReason.InvalidSegment; + if (!Services.NetService.IsSegmentValid(segmentId)) { + reason = SetPrioritySignUnableReason.InvalidSegment; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - bool ret = - (MaySegmentHavePrioritySign(segmentId, true, out reason) || - MaySegmentHavePrioritySign(segmentId, false, out reason)); + bool ret = + (MaySegmentHavePrioritySign(segmentId, true, out reason) || + MaySegmentHavePrioritySign(segmentId, false, out reason)); #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result={ret}, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result={ret}, reason={reason}"); + } #endif - return ret; - } + return ret; + } - public bool HasSegmentPrioritySign(ushort segmentId) { - return !PrioritySegments[segmentId].IsDefault(); - } + public bool HasSegmentPrioritySign(ushort segmentId) { + return !PrioritySegments[segmentId].IsDefault(); + } - public bool HasSegmentPrioritySign(ushort segmentId, bool startNode) { - return PrioritySegments[segmentId].HasPrioritySignAtNode(startNode); - } + public bool HasSegmentPrioritySign(ushort segmentId, bool startNode) { + return PrioritySegments[segmentId].HasPrioritySignAtNode(startNode); + } - public bool HasNodePrioritySign(ushort nodeId) { + public bool HasNodePrioritySign(ushort nodeId) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); #endif - bool ret = false; - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - if (HasSegmentPrioritySign(segmentId, nodeId == segment.m_startNode)) { - ret = true; - return false; - } - return true; - }); + bool ret = false; + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + if (HasSegmentPrioritySign(segmentId, nodeId == segment.m_startNode)) { + ret = true; + return false; + } + return true; + }); #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.HasNodePrioritySign: nodeId={nodeId}, result={ret}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.HasNodePrioritySign: nodeId={nodeId}, result={ret}"); + } #endif - return ret; - } + return ret; + } - public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type) { - SetPrioritySignUnableReason reason; - return SetPrioritySign(segmentId, startNode, type, out reason); - } + public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type) { + SetPrioritySignUnableReason reason; + return SetPrioritySign(segmentId, startNode, type, out reason); + } - public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type, out SetPrioritySignUnableReason reason) { + public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif - bool ret = true; - reason = SetPrioritySignUnableReason.None; + bool ret = true; + reason = SetPrioritySignUnableReason.None; - if (type != PriorityType.None && - ! MaySegmentHavePrioritySign(segmentId, startNode, out reason)) { + if (type != PriorityType.None && + ! MaySegmentHavePrioritySign(segmentId, startNode, out reason)) { #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.SetPrioritySign: Segment {segmentId} @ {startNode} may not have a priority sign: {reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.SetPrioritySign: Segment {segmentId} @ {startNode} may not have a priority sign: {reason}"); + } #endif - ret = false; - type = PriorityType.None; - } + ret = false; + type = PriorityType.None; + } - if (type != PriorityType.None) { - ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, startNode); - Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - TrafficLightManager.Instance.RemoveTrafficLight(nodeId, ref node); - return true; - }); - } + if (type != PriorityType.None) { + ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, startNode); + Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + TrafficLightManager.Instance.RemoveTrafficLight(nodeId, ref node); + return true; + }); + } - if (startNode) { - PrioritySegments[segmentId].startType = type; - } else { - PrioritySegments[segmentId].endType = type; - } + if (startNode) { + PrioritySegments[segmentId].startType = type; + } else { + PrioritySegments[segmentId].endType = type; + } - SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); + SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.SetPrioritySign: segmentId={segmentId}, startNode={startNode}, type={type}, result={ret}, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.SetPrioritySign: segmentId={segmentId}, startNode={startNode}, type={type}, result={ret}, reason={reason}"); + } #endif - return ret; - } + return ret; + } - public void RemovePrioritySignsFromNode(ushort nodeId) { + public void RemovePrioritySignsFromNode(ushort nodeId) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); - if (debug) { - Log._Debug($"TrafficPriorityManager.RemovePrioritySignsFromNode: nodeId={nodeId}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); + if (debug) { + Log._Debug($"TrafficPriorityManager.RemovePrioritySignsFromNode: nodeId={nodeId}"); + } #endif - Services.NetService.IterateNodeSegments(nodeId, delegate(ushort segmentId, ref NetSegment segment) { - RemovePrioritySignFromSegmentEnd(segmentId, nodeId == segment.m_startNode); - return true; - }); - } + Services.NetService.IterateNodeSegments(nodeId, delegate(ushort segmentId, ref NetSegment segment) { + RemovePrioritySignFromSegmentEnd(segmentId, nodeId == segment.m_startNode); + return true; + }); + } - public void RemovePrioritySignsFromSegment(ushort segmentId) { + public void RemovePrioritySignsFromSegment(ushort segmentId) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); - if (debug) { - Log._Debug($"TrafficPriorityManager.RemovePrioritySignsFromSegment: segmentId={segmentId}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + if (debug) { + Log._Debug($"TrafficPriorityManager.RemovePrioritySignsFromSegment: segmentId={segmentId}"); + } #endif - RemovePrioritySignFromSegmentEnd(segmentId, true); - RemovePrioritySignFromSegmentEnd(segmentId, false); - } + RemovePrioritySignFromSegmentEnd(segmentId, true); + RemovePrioritySignFromSegmentEnd(segmentId, false); + } - public void RemovePrioritySignFromSegmentEnd(ushort segmentId, bool startNode) { + public void RemovePrioritySignFromSegmentEnd(ushort segmentId, bool startNode) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); - if (debug) { - Log._Debug($"TrafficPriorityManager.RemovePrioritySignFromSegment: segmentId={segmentId}, startNode={startNode}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + if (debug) { + Log._Debug($"TrafficPriorityManager.RemovePrioritySignFromSegment: segmentId={segmentId}, startNode={startNode}"); + } #endif - if (startNode) { - PrioritySegments[segmentId].startType = PriorityType.None; - } else { - PrioritySegments[segmentId].endType = PriorityType.None; - } + if (startNode) { + PrioritySegments[segmentId].startType = PriorityType.None; + } else { + PrioritySegments[segmentId].endType = PriorityType.None; + } - SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); - } + SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); + } - public PriorityType GetPrioritySign(ushort segmentId, bool startNode) { - return startNode ? PrioritySegments[segmentId].startType : PrioritySegments[segmentId].endType; - } + public PriorityType GetPrioritySign(ushort segmentId, bool startNode) { + return startNode ? PrioritySegments[segmentId].startType : PrioritySegments[segmentId].endType; + } - public byte CountPrioritySignsAtNode(ushort nodeId, PriorityType sign) { + public byte CountPrioritySignsAtNode(ushort nodeId, PriorityType sign) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); #endif - byte ret = 0; - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - if (GetPrioritySign(segmentId, segment.m_startNode == nodeId) == sign) { - ++ret; - } - return true; - }); + byte ret = 0; + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + if (GetPrioritySign(segmentId, segment.m_startNode == nodeId) == sign) { + ++ret; + } + return true; + }); #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.CountPrioritySignsAtNode: nodeId={nodeId}, sign={sign}, result={ret}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.CountPrioritySignsAtNode: nodeId={nodeId}, sign={sign}, result={ret}"); + } #endif - return ret; - } + return ret; + } - public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ref ExtSegmentEnd curEnd, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, ref NetNode transitNode) { - IExtSegmentEndManager extSegEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ref ExtSegmentEnd curEnd, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, ref NetNode transitNode) { + IExtSegmentEndManager extSegEndMan = Constants.ManagerFactory.ExtSegmentEndManager; // ISegmentEndGeometry endGeo = SegmentGeometry.Get(curPos.m_segment)?.GetEnd(startNode); // if (Constants.ManagerFactory.ExtSegmentManager == null) { @@ -312,897 +311,897 @@ public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Posi //#endif // } - /*SegmentEnd end = SegmentEndManager.Instance.GetSegmentEnd(curPos.m_segment, startNode); - if (end == null) { + /*SegmentEnd end = SegmentEndManager.Instance.GetSegmentEnd(curPos.m_segment, startNode); + if (end == null) { #if DEBUG - Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for segment {curPos.m_segment} @ {startNode}"); - return true; + Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for segment {curPos.m_segment} @ {startNode}"); + return true; #endif - } - ushort transitNodeId = end.NodeId;*/ + } + ushort transitNodeId = end.NodeId;*/ #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || transitNodeId == GlobalConfig.Instance.Debug.NodeId); - if (debug) { - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Checking vehicle {vehicleId} at node {transitNodeId}. Coming from seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}, going to seg. {nextPos.m_segment}, lane {nextPos.m_lane}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || transitNodeId == GlobalConfig.Instance.Debug.NodeId); + if (debug) { + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Checking vehicle {vehicleId} at node {transitNodeId}. Coming from seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}, going to seg. {nextPos.m_segment}, lane {nextPos.m_lane}"); + } #else bool debug = false; #endif - if ((vehicle.m_flags & Vehicle.Flags.Spawned) == 0) { + if ((vehicle.m_flags & Vehicle.Flags.Spawned) == 0) { #if DEBUG - if (debug) - Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is not spawned."); + if (debug) + Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is not spawned."); #endif - return true; - } + return true; + } - if ((vehicle.m_flags & Vehicle.Flags.Emergency2) != 0) { - // target vehicle is on emergency + if ((vehicle.m_flags & Vehicle.Flags.Emergency2) != 0) { + // target vehicle is on emergency #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is on emergency."); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is on emergency."); #endif - return true; - } + return true; + } - if (vehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { - // monorails do not obey priority signs + if (vehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { + // monorails do not obey priority signs #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is a monorail."); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is a monorail."); #endif - return true; - } + return true; + } - PriorityType curSign = GetPrioritySign(curPos.m_segment, startNode); - if (curSign == PriorityType.None) { + PriorityType curSign = GetPrioritySign(curPos.m_segment, startNode); + if (curSign == PriorityType.None) { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Sign is None @ seg. {curPos.m_segment}, start {startNode} -> setting to Main"); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Sign is None @ seg. {curPos.m_segment}, start {startNode} -> setting to Main"); #endif - curSign = PriorityType.Main; - } - bool onMain = curSign == PriorityType.Main; + curSign = PriorityType.Main; + } + bool onMain = curSign == PriorityType.Main; - /*if (! Services.VehicleService.IsVehicleValid(vehicleId)) { - curEnd.RequestCleanup(); - return true; - }*/ + /*if (! Services.VehicleService.IsVehicleValid(vehicleId)) { + curEnd.RequestCleanup(); + return true; + }*/ - // calculate approx. time after which the transit node will be reached - Vector3 targetToNode = transitNode.m_position - vehicle.GetLastFramePosition(); - Vector3 targetVel = vehicle.GetLastFrameVelocity(); - float targetSpeed = targetVel.magnitude; - float targetDistanceToTransitNode = targetToNode.magnitude; + // calculate approx. time after which the transit node will be reached + Vector3 targetToNode = transitNode.m_position - vehicle.GetLastFramePosition(); + Vector3 targetVel = vehicle.GetLastFrameVelocity(); + float targetSpeed = targetVel.magnitude; + float targetDistanceToTransitNode = targetToNode.magnitude; - float targetTimeToTransitNode = Single.NaN; - if (targetSpeed > 0) - targetTimeToTransitNode = targetDistanceToTransitNode / targetSpeed; - else - targetTimeToTransitNode = 0; + float targetTimeToTransitNode = Single.NaN; + if (targetSpeed > 0) + targetTimeToTransitNode = targetDistanceToTransitNode / targetSpeed; + else + targetTimeToTransitNode = 0; #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): estimated target time to transit node {transitNodeId} is {targetTimeToTransitNode} for vehicle {vehicleId}"); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): estimated target time to transit node {transitNodeId} is {targetTimeToTransitNode} for vehicle {vehicleId}"); #endif - ArrowDirection targetToDir = extSegEndMan.GetDirection(ref curEnd, nextPos.m_segment); // absolute target direction of target vehicle + ArrowDirection targetToDir = extSegEndMan.GetDirection(ref curEnd, nextPos.m_segment); // absolute target direction of target vehicle - // iterate over all cars approaching the transit node and check if the target vehicle should be prioritized - ExtVehicleManager vehStateManager = ExtVehicleManager.Instance; - CustomSegmentLightsManager segLightsManager = CustomSegmentLightsManager.Instance; + // iterate over all cars approaching the transit node and check if the target vehicle should be prioritized + ExtVehicleManager vehStateManager = ExtVehicleManager.Instance; + CustomSegmentLightsManager segLightsManager = CustomSegmentLightsManager.Instance; - for (int i = 0; i < 8; ++i) { - ushort otherSegmentId = transitNode.GetSegment(i); - if (otherSegmentId == curEnd.segmentId || otherSegmentId == 0) { - continue; - } + for (int i = 0; i < 8; ++i) { + ushort otherSegmentId = transitNode.GetSegment(i); + if (otherSegmentId == curEnd.segmentId || otherSegmentId == 0) { + continue; + } - bool otherStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, transitNodeId); - /*ISegmentEnd incomingEnd = SegmentEndManager.Instance.GetSegmentEnd(otherSegmentId, otherStartNode); - if (incomingEnd == null) { + bool otherStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, transitNodeId); + /*ISegmentEnd incomingEnd = SegmentEndManager.Instance.GetSegmentEnd(otherSegmentId, otherStartNode); + if (incomingEnd == null) { #if DEBUG - if (debug) - Log.Error($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for other segment {otherSegmentId} @ {otherStartNode}"); + if (debug) + Log.Error($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for other segment {otherSegmentId} @ {otherStartNode}"); #endif - return true; - }*/ + return true; + }*/ - ICustomSegmentLights otherLights = null; - if (Options.trafficLightPriorityRules) { - otherLights = segLightsManager.GetSegmentLights(otherSegmentId, otherStartNode, false); - } + ICustomSegmentLights otherLights = null; + if (Options.trafficLightPriorityRules) { + otherLights = segLightsManager.GetSegmentLights(otherSegmentId, otherStartNode, false); + } - PriorityType otherSign = GetPrioritySign(otherSegmentId, otherStartNode); - if (otherSign == PriorityType.None) { - otherSign = PriorityType.Main; - //continue; - } - bool incomingOnMain = otherSign == PriorityType.Main; + PriorityType otherSign = GetPrioritySign(otherSegmentId, otherStartNode); + if (otherSign == PriorityType.None) { + otherSign = PriorityType.Main; + //continue; + } + bool incomingOnMain = otherSign == PriorityType.Main; - ArrowDirection incomingFromDir = extSegEndMan.GetDirection(ref curEnd, otherSegmentId); // absolute incoming direction of incoming vehicle + ArrowDirection incomingFromDir = extSegEndMan.GetDirection(ref curEnd, otherSegmentId); // absolute incoming direction of incoming vehicle #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other segment {otherSegmentId} @ {transitNodeId}"); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other segment {otherSegmentId} @ {transitNodeId}"); #endif - int otherEndIndex = extSegEndMan.GetIndex(otherSegmentId, otherStartNode); - ushort incomingVehicleId = extSegEndMan.ExtSegmentEnds[otherEndIndex].firstVehicleId; - int numIter = 0; - while (incomingVehicleId != 0) { + int otherEndIndex = extSegEndMan.GetIndex(otherSegmentId, otherStartNode); + ushort incomingVehicleId = extSegEndMan.ExtSegmentEnds[otherEndIndex].firstVehicleId; + int numIter = 0; + while (incomingVehicleId != 0) { #if DEBUG - if (debug) { - Log._Debug(""); - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other vehicle {incomingVehicleId} @ seg. {otherSegmentId}"); - } + if (debug) { + Log._Debug(""); + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other vehicle {incomingVehicleId} @ seg. {otherSegmentId}"); + } #endif - if (IsConflictingVehicle(debug, transitNode.m_position, targetTimeToTransitNode, vehicleId, ref vehicle, ref curPos, transitNodeId, startNode, ref nextPos, onMain, ref curEnd, targetToDir, incomingVehicleId, ref Singleton.instance.m_vehicles.m_buffer[incomingVehicleId], ref vehStateManager.ExtVehicles[incomingVehicleId], incomingOnMain, ref extSegEndMan.ExtSegmentEnds[otherEndIndex], otherLights, incomingFromDir)) { + if (IsConflictingVehicle(debug, transitNode.m_position, targetTimeToTransitNode, vehicleId, ref vehicle, ref curPos, transitNodeId, startNode, ref nextPos, onMain, ref curEnd, targetToDir, incomingVehicleId, ref Singleton.instance.m_vehicles.m_buffer[incomingVehicleId], ref vehStateManager.ExtVehicles[incomingVehicleId], incomingOnMain, ref extSegEndMan.ExtSegmentEnds[otherEndIndex], otherLights, incomingFromDir)) { #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): incoming vehicle {incomingVehicleId} is conflicting."); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): incoming vehicle {incomingVehicleId} is conflicting."); + } #endif - return false; - } + return false; + } - // check next incoming vehicle - incomingVehicleId = vehStateManager.ExtVehicles[incomingVehicleId].nextVehicleIdOnSegment; + // check next incoming vehicle + incomingVehicleId = vehStateManager.ExtVehicles[incomingVehicleId].nextVehicleIdOnSegment; - if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): No conflicting incoming vehicles found."); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): No conflicting incoming vehicles found."); + } #endif - return true; - } + return true; + } - private bool IsConflictingVehicle(bool debug, Vector3 transitNodePos, float targetTimeToTransitNode, ushort vehicleId, ref Vehicle vehicle, - ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, bool onMain, ref ExtSegmentEnd curEnd, - ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref ExtVehicle incomingState, bool incomingOnMain, - ref ExtSegmentEnd incomingEnd, ICustomSegmentLights incomingLights, ArrowDirection incomingFromDir) { + private bool IsConflictingVehicle(bool debug, Vector3 transitNodePos, float targetTimeToTransitNode, ushort vehicleId, ref Vehicle vehicle, + ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, bool onMain, ref ExtSegmentEnd curEnd, + ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref ExtVehicle incomingState, bool incomingOnMain, + ref ExtSegmentEnd incomingEnd, ICustomSegmentLights incomingLights, ArrowDirection incomingFromDir) { #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Checking against other vehicle {incomingVehicleId}."); - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): TARGET is coming from seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}, going to seg. {nextPos.m_segment}, lane {nextPos.m_lane}"); - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): INCOMING is coming from seg. {incomingState.currentSegmentId}, start {incomingState.currentStartNode}, lane {incomingState.currentLaneIndex}, going to seg. {incomingState.nextSegmentId}, lane {incomingState.nextLaneIndex}\nincoming state: {incomingState}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Checking against other vehicle {incomingVehicleId}."); + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): TARGET is coming from seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}, going to seg. {nextPos.m_segment}, lane {nextPos.m_lane}"); + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): INCOMING is coming from seg. {incomingState.currentSegmentId}, start {incomingState.currentStartNode}, lane {incomingState.currentLaneIndex}, going to seg. {incomingState.nextSegmentId}, lane {incomingState.nextLaneIndex}\nincoming state: {incomingState}"); + } #endif - if ((incomingState.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + if ((incomingState.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { #if DEBUG - if (debug) - Log.Warning($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is not spawned."); + if (debug) + Log.Warning($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is not spawned."); #endif - return false; - } + return false; + } - if (incomingVehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { - // monorails and cars do not collide + if (incomingVehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { + // monorails and cars do not collide #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is a monorail."); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is a monorail."); + } #endif - return false; - } + return false; + } - ArrowDirection incomingToRelDir = Constants.ManagerFactory.ExtSegmentEndManager.GetDirection(ref incomingEnd, incomingState.nextSegmentId); // relative target direction of incoming vehicle + ArrowDirection incomingToRelDir = Constants.ManagerFactory.ExtSegmentEndManager.GetDirection(ref incomingEnd, incomingState.nextSegmentId); // relative target direction of incoming vehicle - if (incomingLights != null) { - ICustomSegmentLight incomingLight = incomingLights.GetCustomLight(incomingState.currentLaneIndex); + if (incomingLights != null) { + ICustomSegmentLight incomingLight = incomingLights.GetCustomLight(incomingState.currentLaneIndex); #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Detected traffic light. Incoming state ({incomingToRelDir}): {incomingLight.GetLightState(incomingToRelDir)}"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Detected traffic light. Incoming state ({incomingToRelDir}): {incomingLight.GetLightState(incomingToRelDir)}"); #endif - if (incomingLight.IsRed(incomingToRelDir)) { + if (incomingLight.IsRed(incomingToRelDir)) { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming traffic light is red."); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming traffic light is red."); #endif - return false; - } - } + return false; + } + } - if (incomingState.junctionTransitState != VehicleJunctionTransitState.None) { - Vector3 incomingVel = incomingVehicle.GetLastFrameVelocity(); - bool incomingStateChangedRecently = Constants.ManagerFactory.ExtVehicleManager.IsJunctionTransitStateNew(ref incomingState); - if (incomingState.junctionTransitState == VehicleJunctionTransitState.Approach || - incomingState.junctionTransitState == VehicleJunctionTransitState.Leave - ) { - if ((incomingState.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None) { - float incomingSqrSpeed = incomingVel.sqrMagnitude; - if (!incomingStateChangedRecently && incomingSqrSpeed <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { + if (incomingState.junctionTransitState != VehicleJunctionTransitState.None) { + Vector3 incomingVel = incomingVehicle.GetLastFrameVelocity(); + bool incomingStateChangedRecently = Constants.ManagerFactory.ExtVehicleManager.IsJunctionTransitStateNew(ref incomingState); + if (incomingState.junctionTransitState == VehicleJunctionTransitState.Approach || + incomingState.junctionTransitState == VehicleJunctionTransitState.Leave + ) { + if ((incomingState.vehicleType & API.Traffic.Enums.ExtVehicleType.RoadVehicle) != API.Traffic.Enums.ExtVehicleType.None) { + float incomingSqrSpeed = incomingVel.sqrMagnitude; + if (!incomingStateChangedRecently && incomingSqrSpeed <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is LEAVING or APPROACHING but not moving. -> BLOCKED"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is LEAVING or APPROACHING but not moving. -> BLOCKED"); #endif - Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref incomingState, VehicleJunctionTransitState.Blocked); - incomingStateChangedRecently = true; - return false; - } - } + Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref incomingState, VehicleJunctionTransitState.Blocked); + incomingStateChangedRecently = true; + return false; + } + } - // incoming vehicle is (1) entering the junction or (2) leaving - Vector3 incomingPos = incomingVehicle.GetLastFramePosition(); - Vector3 incomingToNode = transitNodePos - incomingPos; + // incoming vehicle is (1) entering the junction or (2) leaving + Vector3 incomingPos = incomingVehicle.GetLastFramePosition(); + Vector3 incomingToNode = transitNodePos - incomingPos; - // check if incoming vehicle moves towards node - float dot = Vector3.Dot(incomingToNode, incomingVel); - if (dot <= 0) { + // check if incoming vehicle moves towards node + float dot = Vector3.Dot(incomingToNode, incomingVel); + if (dot <= 0) { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving away from the transit node ({dot}). *IGNORING*"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving away from the transit node ({dot}). *IGNORING*"); #endif - return false; - } + return false; + } #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving towards the transit node ({dot}). Distance: {incomingToNode.magnitude}"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving towards the transit node ({dot}). Distance: {incomingToNode.magnitude}"); #endif - // check if estimated approach time of the incoming vehicle is within bounds (only if incoming vehicle is far enough away from the junction and target vehicle is moving) - if (!Single.IsInfinity(targetTimeToTransitNode) && - !Single.IsNaN(targetTimeToTransitNode) && - incomingToNode.sqrMagnitude > GlobalConfig.Instance.PriorityRules.MaxPriorityCheckSqrDist - ) { + // check if estimated approach time of the incoming vehicle is within bounds (only if incoming vehicle is far enough away from the junction and target vehicle is moving) + if (!Single.IsInfinity(targetTimeToTransitNode) && + !Single.IsNaN(targetTimeToTransitNode) && + incomingToNode.sqrMagnitude > GlobalConfig.Instance.PriorityRules.MaxPriorityCheckSqrDist + ) { - // check speeds - float incomingSpeed = incomingVel.magnitude; - float incomingDistanceToTransitNode = incomingToNode.magnitude; - float incomingTimeToTransitNode = Single.NaN; + // check speeds + float incomingSpeed = incomingVel.magnitude; + float incomingDistanceToTransitNode = incomingToNode.magnitude; + float incomingTimeToTransitNode = Single.NaN; - if (incomingSpeed > 0) - incomingTimeToTransitNode = incomingDistanceToTransitNode / incomingSpeed; - else - incomingTimeToTransitNode = Single.PositiveInfinity; + if (incomingSpeed > 0) + incomingTimeToTransitNode = incomingDistanceToTransitNode / incomingSpeed; + else + incomingTimeToTransitNode = Single.PositiveInfinity; - float timeDiff = Mathf.Abs(incomingTimeToTransitNode - targetTimeToTransitNode); - if (timeDiff > GlobalConfig.Instance.PriorityRules.MaxPriorityApproachTime) { + float timeDiff = Mathf.Abs(incomingTimeToTransitNode - targetTimeToTransitNode); + if (timeDiff > GlobalConfig.Instance.PriorityRules.MaxPriorityApproachTime) { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} needs {incomingTimeToTransitNode} time units to get to the node where target needs {targetTimeToTransitNode} time units (diff = {timeDiff}). Difference to large. *IGNORING*"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} needs {incomingTimeToTransitNode} time units to get to the node where target needs {targetTimeToTransitNode} time units (diff = {timeDiff}). Difference to large. *IGNORING*"); #endif - return false; - } else { + return false; + } else { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} needs {incomingTimeToTransitNode} time units to get to the node where target needs {targetTimeToTransitNode} time units (diff = {timeDiff}). Difference within bounds. Priority check required."); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} needs {incomingTimeToTransitNode} time units to get to the node where target needs {targetTimeToTransitNode} time units (diff = {timeDiff}). Difference within bounds. Priority check required."); #endif - } - } else { + } + } else { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming is stopped."); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming is stopped."); #endif - } - } + } + } - if (!incomingStateChangedRecently && - (incomingState.junctionTransitState == VehicleJunctionTransitState.Blocked/* || + if (!incomingStateChangedRecently && + (incomingState.junctionTransitState == VehicleJunctionTransitState.Blocked/* || (incomingState.JunctionTransitState == VehicleJunctionTransitState.Stop && vehicleId < incomingVehicleId)*/) - ) { -#if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is BLOCKED and has waited a bit or is STOP and targetVehicleId {vehicleId} < incomingVehicleId {incomingVehicleId}. *IGNORING*"); -#endif - - // incoming vehicle waits because the junction is blocked or it does not get priority and we waited for some time. Allow target vehicle to enter the junciton. - return false; - } - - // check priority rules - if (HasVehiclePriority(debug, vehicleId, ref vehicle, ref curPos, transitNodeId, startNode, ref nextPos, onMain, targetToDir, incomingVehicleId, ref incomingVehicle, ref incomingState, incomingOnMain, incomingFromDir, incomingToRelDir)) { -#if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is not conflicting."); -#endif - return false; - } else { -#if DEBUG - if (debug) - Log._Debug($"==========> TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} IS conflicting."); -#endif - return true; - } - } else { -#if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} (main) is not conflicting ({incomingState.junctionTransitState})."); -#endif - return false; - } - } - - /// - /// Implements priority checking for two vehicles approaching or waiting at a junction. - /// - /// - /// id of the junction - /// target vehicle for which priority is being checked - /// target vehicle data - /// target vehicle current path position - /// target vehicle next path position - /// true if the target vehicle is coming from a main road - /// possibly conflicting incoming vehicle - /// incoming vehicle current path position - /// incoming vehicle next path position - /// true if the incoming vehicle is coming from a main road - /// true if the target vehicle has priority, false otherwise - private bool HasVehiclePriority(bool debug, ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, - bool onMain, ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref ExtVehicle incomingState, bool incomingOnMain, - ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir) { -#if DEBUG - if (debug) { - Log._Debug(""); - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): *** Checking if vehicle {vehicleId} (main road = {onMain}) @ (seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}) -> (seg. {nextPos.m_segment}, lane {nextPos.m_lane}) has priority over {incomingVehicleId} (main road = {incomingOnMain}) @ (seg. {incomingState.currentSegmentId}, start {incomingState.currentStartNode}, lane {incomingState.currentLaneIndex}) -> (seg. {incomingState.nextSegmentId}, lane {incomingState.nextLaneIndex})."); - } + ) { +#if DEBUG + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is BLOCKED and has waited a bit or is STOP and targetVehicleId {vehicleId} < incomingVehicleId {incomingVehicleId}. *IGNORING*"); #endif - if (targetToDir == ArrowDirection.None || incomingFromDir == ArrowDirection.None || incomingToRelDir == ArrowDirection.None) { + // incoming vehicle waits because the junction is blocked or it does not get priority and we waited for some time. Allow target vehicle to enter the junciton. + return false; + } + + // check priority rules + if (HasVehiclePriority(debug, vehicleId, ref vehicle, ref curPos, transitNodeId, startNode, ref nextPos, onMain, targetToDir, incomingVehicleId, ref incomingVehicle, ref incomingState, incomingOnMain, incomingFromDir, incomingToRelDir)) { #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Invalid directions given: targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); - } + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is not conflicting."); #endif - return true; - } - - if (curPos.m_segment == incomingState.currentSegmentId) { - // both vehicles are coming from the same segment. do not apply priority rules in this case. + return false; + } else { +#if DEBUG + if (debug) + Log._Debug($"==========> TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} IS conflicting."); +#endif + return true; + } + } else { #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Both vehicles come from the same segment. *IGNORING*"); - } + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} (main) is not conflicting ({incomingState.junctionTransitState})."); +#endif + return false; + } + } + + /// + /// Implements priority checking for two vehicles approaching or waiting at a junction. + /// + /// + /// id of the junction + /// target vehicle for which priority is being checked + /// target vehicle data + /// target vehicle current path position + /// target vehicle next path position + /// true if the target vehicle is coming from a main road + /// possibly conflicting incoming vehicle + /// incoming vehicle current path position + /// incoming vehicle next path position + /// true if the incoming vehicle is coming from a main road + /// true if the target vehicle has priority, false otherwise + private bool HasVehiclePriority(bool debug, ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, + bool onMain, ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref ExtVehicle incomingState, bool incomingOnMain, + ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir) { +#if DEBUG + if (debug) { + Log._Debug(""); + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): *** Checking if vehicle {vehicleId} (main road = {onMain}) @ (seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}) -> (seg. {nextPos.m_segment}, lane {nextPos.m_lane}) has priority over {incomingVehicleId} (main road = {incomingOnMain}) @ (seg. {incomingState.currentSegmentId}, start {incomingState.currentStartNode}, lane {incomingState.currentLaneIndex}) -> (seg. {incomingState.nextSegmentId}, lane {incomingState.nextLaneIndex})."); + } #endif - return true; - } - /* FORWARD - * | - * | - * LEFT --- + --- RIGHT - * | - * | - * TURN + if (targetToDir == ArrowDirection.None || incomingFromDir == ArrowDirection.None || incomingToRelDir == ArrowDirection.None) { +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Invalid directions given: targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); + } +#endif + return true; + } - /* - * - Target car is always coming from TURN. - * - Target car is going to `targetToDir` (relative to TURN). - * - Incoming car is coming from `incomingFromDir` (relative to TURN). - * - Incoming car is going to `incomingToRelDir` (relative to `incomingFromDir`). - */ + if (curPos.m_segment == incomingState.currentSegmentId) { + // both vehicles are coming from the same segment. do not apply priority rules in this case. #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): targetToDir: {targetToDir.ToString()}, incomingFromDir: {incomingFromDir.ToString()}, incomingToRelDir: {incomingToRelDir.ToString()}"); + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Both vehicles come from the same segment. *IGNORING*"); + } +#endif + return true; + } + + /* FORWARD + * | + * | + * LEFT --- + --- RIGHT + * | + * | + * TURN + + /* + * - Target car is always coming from TURN. + * - Target car is going to `targetToDir` (relative to TURN). + * - Incoming car is coming from `incomingFromDir` (relative to TURN). + * - Incoming car is going to `incomingToRelDir` (relative to `incomingFromDir`). + */ +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): targetToDir: {targetToDir.ToString()}, incomingFromDir: {incomingFromDir.ToString()}, incomingToRelDir: {incomingToRelDir.ToString()}"); } #endif - if (Services.SimulationService.LeftHandDrive) { - // mirror situation for left-hand traffic systems - targetToDir = ArrowDirectionUtil.InvertLeftRight(targetToDir); - incomingFromDir = ArrowDirectionUtil.InvertLeftRight(incomingFromDir); - incomingToRelDir = ArrowDirectionUtil.InvertLeftRight(incomingToRelDir); + if (Services.SimulationService.LeftHandDrive) { + // mirror situation for left-hand traffic systems + targetToDir = ArrowDirectionUtil.InvertLeftRight(targetToDir); + incomingFromDir = ArrowDirectionUtil.InvertLeftRight(incomingFromDir); + incomingToRelDir = ArrowDirectionUtil.InvertLeftRight(incomingToRelDir); #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): LHD! targetToDir: {targetToDir.ToString()}, incomingFromDir: {incomingFromDir.ToString()}, incomingToRelDir: {incomingToRelDir.ToString()}"); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): LHD! targetToDir: {targetToDir.ToString()}, incomingFromDir: {incomingFromDir.ToString()}, incomingToRelDir: {incomingToRelDir.ToString()}"); + } #endif - } + } #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); + } #endif - /* - * (1) COLLISION DETECTION - */ + /* + * (1) COLLISION DETECTION + */ - bool sameTargets = nextPos.m_segment == incomingState.nextSegmentId; - bool wouldCollide = DetectCollision(debug, ref curPos, transitNodeId, startNode, ref nextPos, ref incomingState, targetToDir, incomingFromDir, incomingToRelDir, vehicleId, incomingVehicleId); + bool sameTargets = nextPos.m_segment == incomingState.nextSegmentId; + bool wouldCollide = DetectCollision(debug, ref curPos, transitNodeId, startNode, ref nextPos, ref incomingState, targetToDir, incomingFromDir, incomingToRelDir, vehicleId, incomingVehicleId); - if (!wouldCollide) { - // both vehicles would not collide. allow both to pass. + if (!wouldCollide) { + // both vehicles would not collide. allow both to pass. #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would not collide. NO CONFLICT."); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would not collide. NO CONFLICT."); + } #endif - return true; - } + return true; + } - // -> vehicles would collide + // -> vehicles would collide #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would collide. Checking priority rules."); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would collide. Checking priority rules."); + } #endif - /* - * (2) CHECK PRIORITY RULES - */ + /* + * (2) CHECK PRIORITY RULES + */ - bool ret; - if ((!onMain && !incomingOnMain) || (onMain && incomingOnMain)) { - // both vehicles are on the same priority level: check common priority rules (left yields to right, left turning vehicles yield to others) - ret = HasPriorityOnSameLevel(debug, targetToDir, incomingFromDir, incomingToRelDir, vehicleId, incomingVehicleId); + bool ret; + if ((!onMain && !incomingOnMain) || (onMain && incomingOnMain)) { + // both vehicles are on the same priority level: check common priority rules (left yields to right, left turning vehicles yield to others) + ret = HasPriorityOnSameLevel(debug, targetToDir, incomingFromDir, incomingToRelDir, vehicleId, incomingVehicleId); #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} are on the same priority level. Checking commong priority rules. ret={ret}"); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} are on the same priority level. Checking commong priority rules. ret={ret}"); + } #endif - } else { - // both vehicles are on a different priority level: prioritize vehicle on main road - ret = onMain; + } else { + // both vehicles are on a different priority level: prioritize vehicle on main road + ret = onMain; #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} are on a different priority. Prioritizing vehicle on main road. ret={ret}"); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} are on a different priority. Prioritizing vehicle on main road. ret={ret}"); + } #endif - } + } - if (ret) { - // check if the incoming vehicle is leaving (though the target vehicle has priority) - bool incomingIsLeaving = incomingState.junctionTransitState == VehicleJunctionTransitState.Leave; + if (ret) { + // check if the incoming vehicle is leaving (though the target vehicle has priority) + bool incomingIsLeaving = incomingState.junctionTransitState == VehicleJunctionTransitState.Leave; #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): >>> Car {vehicleId} has priority over {incomingVehicleId}. incomingIsLeaving={incomingIsLeaving}"); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): >>> Car {vehicleId} has priority over {incomingVehicleId}. incomingIsLeaving={incomingIsLeaving}"); + } #endif - return !incomingIsLeaving; - } else { - // the target vehicle must wait + return !incomingIsLeaving; + } else { + // the target vehicle must wait #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): >>> Car {vehicleId} must wait for {incomingVehicleId}. returning FALSE."); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): >>> Car {vehicleId} must wait for {incomingVehicleId}. returning FALSE."); + } +#endif + + return false; + } + } + + /// + /// Checks if two vehicles are on a collision course. + /// + /// enable debugging + /// junction node + /// incoming vehicle state + /// absolute target vehicle destination direction + /// absolute incoming vehicle source direction + /// relative incoming vehicle destination direction + /// (optional) target vehicle id + /// (optional) incoming vehicle id + /// true if both vehicles are on a collision course, false otherwise + public bool DetectCollision(bool debug, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, + ref ExtVehicle incomingState, ArrowDirection targetToDir, ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir, ushort vehicleId=0, ushort incomingVehicleId=0 + ) { + bool sameTargets = nextPos.m_segment == incomingState.nextSegmentId; + bool wouldCollide; + bool incomingIsLeaving = incomingState.junctionTransitState == VehicleJunctionTransitState.Leave; + if (sameTargets) { + // both are going to the same segment +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment."); + } +#endif + + if (nextPos.m_lane == incomingState.nextLaneIndex) { + // both are going to the same lane: lane order is always incorrect +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment AND lane. lane order is incorrect!"); + } +#endif + wouldCollide = true; + } else { + // both are going to a different lane: check lane order +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment BUT NOT to the same lane. Determining if lane order is correct."); + } +#endif + switch (targetToDir) { + case ArrowDirection.Left: + case ArrowDirection.Turn: + default: // (should not happen) + // target & incoming are going left: stay left + wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, nextPos.m_lane, incomingState.nextLaneIndex); // stay left +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}. Checking if lane {nextPos.m_lane} is LEFT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); + } +#endif + break; + case ArrowDirection.Forward: + // target is going forward/turn + switch (incomingFromDir) { + case ArrowDirection.Left: + case ArrowDirection.Forward: + // target is going forward, incoming is coming from left/forward: stay right + wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, incomingState.nextLaneIndex, nextPos.m_lane); // stay right +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir}. Checking if lane {nextPos.m_lane} is RIGHT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); + } +#endif + break; + case ArrowDirection.Right: + // target is going forward, incoming is coming from right: stay left + wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, nextPos.m_lane, incomingState.nextLaneIndex); // stay left +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir}. Checking if lane {nextPos.m_lane} is LEFT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); + } +#endif + break; + case ArrowDirection.Turn: // (should not happen) + default: // (should not happen) + wouldCollide = false; +#if DEBUG + if (debug) { + Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir} (SHOULD NOT HAPPEN). would collide? {wouldCollide}"); + } +#endif + break; + } + break; + case ArrowDirection.Right: + // target is going right: stay right + wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, incomingState.nextLaneIndex, nextPos.m_lane); // stay right +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going RIGHT. Checking if lane {nextPos.m_lane} is RIGHT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); + } +#endif + break; + } +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): >>> would collide? {wouldCollide}"); + } +#endif + } + } else { +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to different segments."); + } +#endif + switch (targetToDir) { + case ArrowDirection.Left: + switch (incomingFromDir) { + case ArrowDirection.Left: + wouldCollide = incomingToRelDir != ArrowDirection.Right; + break; + case ArrowDirection.Forward: + wouldCollide = incomingToRelDir != ArrowDirection.Left && incomingToRelDir != ArrowDirection.Turn; + break; + case ArrowDirection.Right: + wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Turn; + break; + default: // (should not happen) + wouldCollide = false; +#if DEBUG + if (debug) { + Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. SHOULD NOT HAPPEN. would collide? {wouldCollide}"); + } +#endif + break; + } + break; + case ArrowDirection.Forward: + switch (incomingFromDir) { + case ArrowDirection.Left: + wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Turn; + break; + case ArrowDirection.Forward: + wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Forward; + break; + case ArrowDirection.Right: + wouldCollide = true; // TODO allow u-turns? + break; + default: // (should not happen) + wouldCollide = false; +#if DEBUG + if (debug) { + Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. SHOULD NOT HAPPEN. would collide? {wouldCollide}"); + } +#endif + break; + } + break; + case ArrowDirection.Right: + case ArrowDirection.Turn: + default: + wouldCollide = false; + break; + } +#if DEBUG + if (debug) { + Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. would collide? {wouldCollide}"); + } #endif - - return false; - } - } - - /// - /// Checks if two vehicles are on a collision course. - /// - /// enable debugging - /// junction node - /// incoming vehicle state - /// absolute target vehicle destination direction - /// absolute incoming vehicle source direction - /// relative incoming vehicle destination direction - /// (optional) target vehicle id - /// (optional) incoming vehicle id - /// true if both vehicles are on a collision course, false otherwise - public bool DetectCollision(bool debug, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, - ref ExtVehicle incomingState, ArrowDirection targetToDir, ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir, ushort vehicleId=0, ushort incomingVehicleId=0 - ) { - bool sameTargets = nextPos.m_segment == incomingState.nextSegmentId; - bool wouldCollide; - bool incomingIsLeaving = incomingState.junctionTransitState == VehicleJunctionTransitState.Leave; - if (sameTargets) { - // both are going to the same segment -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment."); - } -#endif - - if (nextPos.m_lane == incomingState.nextLaneIndex) { - // both are going to the same lane: lane order is always incorrect -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment AND lane. lane order is incorrect!"); - } -#endif - wouldCollide = true; - } else { - // both are going to a different lane: check lane order -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment BUT NOT to the same lane. Determining if lane order is correct."); - } -#endif - switch (targetToDir) { - case ArrowDirection.Left: - case ArrowDirection.Turn: - default: // (should not happen) - // target & incoming are going left: stay left - wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, nextPos.m_lane, incomingState.nextLaneIndex); // stay left -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}. Checking if lane {nextPos.m_lane} is LEFT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); - } -#endif - break; - case ArrowDirection.Forward: - // target is going forward/turn - switch (incomingFromDir) { - case ArrowDirection.Left: - case ArrowDirection.Forward: - // target is going forward, incoming is coming from left/forward: stay right - wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, incomingState.nextLaneIndex, nextPos.m_lane); // stay right -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir}. Checking if lane {nextPos.m_lane} is RIGHT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); - } -#endif - break; - case ArrowDirection.Right: - // target is going forward, incoming is coming from right: stay left - wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, nextPos.m_lane, incomingState.nextLaneIndex); // stay left -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir}. Checking if lane {nextPos.m_lane} is LEFT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); - } -#endif - break; - case ArrowDirection.Turn: // (should not happen) - default: // (should not happen) - wouldCollide = false; -#if DEBUG - if (debug) { - Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir} (SHOULD NOT HAPPEN). would collide? {wouldCollide}"); - } -#endif - break; - } - break; - case ArrowDirection.Right: - // target is going right: stay right - wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, incomingState.nextLaneIndex, nextPos.m_lane); // stay right -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going RIGHT. Checking if lane {nextPos.m_lane} is RIGHT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); - } -#endif - break; - } -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): >>> would collide? {wouldCollide}"); - } -#endif - } - } else { -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to different segments."); - } -#endif - switch (targetToDir) { - case ArrowDirection.Left: - switch (incomingFromDir) { - case ArrowDirection.Left: - wouldCollide = incomingToRelDir != ArrowDirection.Right; - break; - case ArrowDirection.Forward: - wouldCollide = incomingToRelDir != ArrowDirection.Left && incomingToRelDir != ArrowDirection.Turn; - break; - case ArrowDirection.Right: - wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Turn; - break; - default: // (should not happen) - wouldCollide = false; -#if DEBUG - if (debug) { - Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. SHOULD NOT HAPPEN. would collide? {wouldCollide}"); - } -#endif - break; - } - break; - case ArrowDirection.Forward: - switch (incomingFromDir) { - case ArrowDirection.Left: - wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Turn; - break; - case ArrowDirection.Forward: - wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Forward; - break; - case ArrowDirection.Right: - wouldCollide = true; // TODO allow u-turns? - break; - default: // (should not happen) - wouldCollide = false; -#if DEBUG - if (debug) { - Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. SHOULD NOT HAPPEN. would collide? {wouldCollide}"); - } -#endif - break; - } - break; - case ArrowDirection.Right: - case ArrowDirection.Turn: - default: - wouldCollide = false; - break; - } -#if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. would collide? {wouldCollide}"); - } -#endif - } - - return wouldCollide; - } - - /// - /// Check common priority rules if both vehicles are on a collision course and on the same priority level [(main AND main) OR (!main AND !main)]: - /// 1. left yields to right - /// 2. left-turning vehicles must yield to straight-going vehicles - /// - /// enable debugging - /// absolute target vehicle destination direction - /// absolute incoming vehicle source direction - /// relative incoming vehicle destination direction - /// (optional) target vehicle id - /// (optional) incoming vehicle id - /// - public bool HasPriorityOnSameLevel(bool debug, ArrowDirection targetToDir, ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir, ushort vehicleId=0, ushort incomingVehicleId=0) { - bool ret; - switch (incomingFromDir) { - case ArrowDirection.Left: - case ArrowDirection.Right: - // (1) left yields to right - ret = incomingFromDir == ArrowDirection.Left; - break; - default: - if (incomingToRelDir == ArrowDirection.Left || incomingToRelDir == ArrowDirection.Turn) { - // (2) incoming vehicle must wait - ret = true; - } else if (targetToDir == ArrowDirection.Left || targetToDir == ArrowDirection.Turn) { - // (2) target vehicle must wait - ret = false; - } else { - // (should not happen) -#if DEBUG - if (debug) { - Log.Warning($"TrafficPriorityManager.HasPriorityOnSameLevel({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}: SHOULD NOT HAPPEN"); - } -#endif - ret = true; - } - break; - } - -#if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.HasPriorityOnSameLevel({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}: ret={ret}"); - } -#endif - - return ret; - } - - /// - /// Checks if lane lies to the left of lane . - /// - /// enable debugging - /// segment id - /// transit node id - /// lane index that is checked to lie left - /// lane index that is checked to lie right - /// - public bool IsLaneOrderConflictFree(bool debug, ushort segmentId, ushort nodeId, byte leftLaneIndex, byte rightLaneIndex) { // TODO refactor - try { - if (leftLaneIndex == rightLaneIndex) { - return false; - } - - NetManager netManager = Singleton.instance; - - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - - NetInfo.Direction dir = nodeId == netManager.m_segments.m_buffer[segmentId].m_startNode ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; - NetInfo.Direction dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); - NetInfo.Direction dir3 = Services.SimulationService.LeftHandDrive ? NetInfo.InvertDirection(dir2) : dir2; - - NetInfo.Lane leftLane = segmentInfo.m_lanes[leftLaneIndex]; - NetInfo.Lane rightLane = segmentInfo.m_lanes[rightLaneIndex]; - -#if DEBUG - if (debug) { - Log._Debug($" IsLaneOrderConflictFree({segmentId}, {leftLaneIndex}, {rightLaneIndex}): dir={dir}, dir2={dir2}, dir3={dir3} laneDir={leftLane.m_direction}, leftLanePos={leftLane.m_position}, rightLanePos={rightLane.m_position}"); - } -#endif - - bool ret = (dir3 == NetInfo.Direction.Forward) ^ (leftLane.m_position < rightLane.m_position); - return ret; - } catch (Exception e) { - Log.Error($"IsLaneOrderConflictFree({segmentId}, {leftLaneIndex}, {rightLaneIndex}): Error: {e.ToString()}"); } - return true; - } - - protected override void HandleInvalidSegment(ref ExtSegment seg) { - if (!PrioritySegments[seg.segmentId].IsDefault()) { - AddInvalidPrioritySegment(seg.segmentId, ref PrioritySegments[seg.segmentId]); - } - RemovePrioritySignsFromSegment(seg.segmentId); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - if (! MaySegmentHavePrioritySign(seg.segmentId, true)) { - RemovePrioritySignFromSegmentEnd(seg.segmentId, true); - } else { - UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, true)); - } - - if (!MaySegmentHavePrioritySign(seg.segmentId, false)) { - RemovePrioritySignFromSegmentEnd(seg.segmentId, false); - } else { - UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, false)); - } - } - - protected override void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { - ISegmentEndId oldSegmentEndId = replacement.oldSegmentEndId; - ISegmentEndId newSegmentEndId = replacement.newSegmentEndId; - - PriorityType sign = PriorityType.None; - if (oldSegmentEndId.StartNode) { - sign = invalidPrioritySegments[oldSegmentEndId.SegmentId].startType; - invalidPrioritySegments[oldSegmentEndId.SegmentId].startType = PriorityType.None; - } else { - sign = invalidPrioritySegments[oldSegmentEndId.SegmentId].endType; - invalidPrioritySegments[oldSegmentEndId.SegmentId].endType = PriorityType.None; - } - - if (sign == PriorityType.None) { - return; - } - - Log._Debug($"TrafficPriorityManager.HandleSegmentEndReplacement({replacement}): Segment replacement detected: {oldSegmentEndId.SegmentId} -> {newSegmentEndId.SegmentId}\n" + - $"Moving priority sign {sign} to new segment." - ); - - SetPrioritySign(newSegmentEndId.SegmentId, newSegmentEndId.StartNode, sign); - } - - protected void UpdateNode(ushort nodeId) { - SetPrioritySignUnableReason reason; - if (! MayNodeHavePrioritySigns(nodeId, out reason)) { - RemovePrioritySignsFromNode(nodeId); - return; - } - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - for (int i = 0; i < PrioritySegments.Length; ++i) { - RemovePrioritySignsFromSegment((ushort)i); - } - for (int i = 0; i < invalidPrioritySegments.Length; ++i) { - invalidPrioritySegments[i].Reset(); - } - } - - [Obsolete] - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading {data.Count} priority segments (old method)"); - foreach (var segment in data) { - try { - if (segment.Length < 3) - continue; - - if ((PriorityType)segment[2] == PriorityType.None) { - continue; - } - - ushort nodeId = (ushort)segment[0]; - ushort segmentId = (ushort)segment[1]; - PriorityType sign = (PriorityType)segment[2]; - - if (!Services.NetService.IsNodeValid(nodeId)) { - continue; - } - if (!Services.NetService.IsSegmentValid(segmentId)) { - continue; - } - - bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); - if (startNode == null) { - Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {nodeId} @ seg. {segmentId}"); - continue; - } - - SetPrioritySign(segmentId, (bool)startNode, sign); - } catch (Exception e) { - // ignore, as it's probably corrupt save data. it'll be culled on next save - Log.Warning("Error loading data from Priority segments: " + e.ToString()); - success = false; - } - } - return success; - } - - [Obsolete] - public List SaveData(ref bool success) { - return null; - } - - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading {data.Count} priority segments (new method)"); - foreach (var prioSegData in data) { - try { - if ((PriorityType)prioSegData.priorityType == PriorityType.None) { - continue; - } - if (!Services.NetService.IsNodeValid(prioSegData.nodeId)) { - continue; - } - if (!Services.NetService.IsSegmentValid(prioSegData.segmentId)) { - continue; - } - bool? startNode = Services.NetService.IsStartNode(prioSegData.segmentId, prioSegData.nodeId); - if (startNode == null) { - Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {prioSegData.nodeId} @ seg. {prioSegData.segmentId}"); - continue; - } + + return wouldCollide; + } + + /// + /// Check common priority rules if both vehicles are on a collision course and on the same priority level [(main AND main) OR (!main AND !main)]: + /// 1. left yields to right + /// 2. left-turning vehicles must yield to straight-going vehicles + /// + /// enable debugging + /// absolute target vehicle destination direction + /// absolute incoming vehicle source direction + /// relative incoming vehicle destination direction + /// (optional) target vehicle id + /// (optional) incoming vehicle id + /// + public bool HasPriorityOnSameLevel(bool debug, ArrowDirection targetToDir, ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir, ushort vehicleId=0, ushort incomingVehicleId=0) { + bool ret; + switch (incomingFromDir) { + case ArrowDirection.Left: + case ArrowDirection.Right: + // (1) left yields to right + ret = incomingFromDir == ArrowDirection.Left; + break; + default: + if (incomingToRelDir == ArrowDirection.Left || incomingToRelDir == ArrowDirection.Turn) { + // (2) incoming vehicle must wait + ret = true; + } else if (targetToDir == ArrowDirection.Left || targetToDir == ArrowDirection.Turn) { + // (2) target vehicle must wait + ret = false; + } else { + // (should not happen) +#if DEBUG + if (debug) { + Log.Warning($"TrafficPriorityManager.HasPriorityOnSameLevel({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}: SHOULD NOT HAPPEN"); + } +#endif + ret = true; + } + break; + } + +#if DEBUG + if (debug) { + Log._Debug($"TrafficPriorityManager.HasPriorityOnSameLevel({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}: ret={ret}"); + } +#endif + + return ret; + } + + /// + /// Checks if lane lies to the left of lane . + /// + /// enable debugging + /// segment id + /// transit node id + /// lane index that is checked to lie left + /// lane index that is checked to lie right + /// + public bool IsLaneOrderConflictFree(bool debug, ushort segmentId, ushort nodeId, byte leftLaneIndex, byte rightLaneIndex) { // TODO refactor + try { + if (leftLaneIndex == rightLaneIndex) { + return false; + } + + NetManager netManager = Singleton.instance; + + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + + NetInfo.Direction dir = nodeId == netManager.m_segments.m_buffer[segmentId].m_startNode ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; + NetInfo.Direction dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); + NetInfo.Direction dir3 = Services.SimulationService.LeftHandDrive ? NetInfo.InvertDirection(dir2) : dir2; + + NetInfo.Lane leftLane = segmentInfo.m_lanes[leftLaneIndex]; + NetInfo.Lane rightLane = segmentInfo.m_lanes[rightLaneIndex]; + +#if DEBUG + if (debug) { + Log._Debug($" IsLaneOrderConflictFree({segmentId}, {leftLaneIndex}, {rightLaneIndex}): dir={dir}, dir2={dir2}, dir3={dir3} laneDir={leftLane.m_direction}, leftLanePos={leftLane.m_position}, rightLanePos={rightLane.m_position}"); + } +#endif + + bool ret = (dir3 == NetInfo.Direction.Forward) ^ (leftLane.m_position < rightLane.m_position); + return ret; + } catch (Exception e) { + Log.Error($"IsLaneOrderConflictFree({segmentId}, {leftLaneIndex}, {rightLaneIndex}): Error: {e.ToString()}"); + } + return true; + } + + protected override void HandleInvalidSegment(ref ExtSegment seg) { + if (!PrioritySegments[seg.segmentId].IsDefault()) { + AddInvalidPrioritySegment(seg.segmentId, ref PrioritySegments[seg.segmentId]); + } + RemovePrioritySignsFromSegment(seg.segmentId); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + if (! MaySegmentHavePrioritySign(seg.segmentId, true)) { + RemovePrioritySignFromSegmentEnd(seg.segmentId, true); + } else { + UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, true)); + } + + if (!MaySegmentHavePrioritySign(seg.segmentId, false)) { + RemovePrioritySignFromSegmentEnd(seg.segmentId, false); + } else { + UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, false)); + } + } + + protected override void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { + ISegmentEndId oldSegmentEndId = replacement.oldSegmentEndId; + ISegmentEndId newSegmentEndId = replacement.newSegmentEndId; + + PriorityType sign = PriorityType.None; + if (oldSegmentEndId.StartNode) { + sign = invalidPrioritySegments[oldSegmentEndId.SegmentId].startType; + invalidPrioritySegments[oldSegmentEndId.SegmentId].startType = PriorityType.None; + } else { + sign = invalidPrioritySegments[oldSegmentEndId.SegmentId].endType; + invalidPrioritySegments[oldSegmentEndId.SegmentId].endType = PriorityType.None; + } + + if (sign == PriorityType.None) { + return; + } + + Log._Debug($"TrafficPriorityManager.HandleSegmentEndReplacement({replacement}): Segment replacement detected: {oldSegmentEndId.SegmentId} -> {newSegmentEndId.SegmentId}\n" + + $"Moving priority sign {sign} to new segment." + ); + + SetPrioritySign(newSegmentEndId.SegmentId, newSegmentEndId.StartNode, sign); + } + + protected void UpdateNode(ushort nodeId) { + SetPrioritySignUnableReason reason; + if (! MayNodeHavePrioritySigns(nodeId, out reason)) { + RemovePrioritySignsFromNode(nodeId); + return; + } + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + for (int i = 0; i < PrioritySegments.Length; ++i) { + RemovePrioritySignsFromSegment((ushort)i); + } + for (int i = 0; i < invalidPrioritySegments.Length; ++i) { + invalidPrioritySegments[i].Reset(); + } + } + + [Obsolete] + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading {data.Count} priority segments (old method)"); + foreach (var segment in data) { + try { + if (segment.Length < 3) + continue; + + if ((PriorityType)segment[2] == PriorityType.None) { + continue; + } + + ushort nodeId = (ushort)segment[0]; + ushort segmentId = (ushort)segment[1]; + PriorityType sign = (PriorityType)segment[2]; + + if (!Services.NetService.IsNodeValid(nodeId)) { + continue; + } + if (!Services.NetService.IsSegmentValid(segmentId)) { + continue; + } + + bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); + if (startNode == null) { + Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {nodeId} @ seg. {segmentId}"); + continue; + } + + SetPrioritySign(segmentId, (bool)startNode, sign); + } catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Warning("Error loading data from Priority segments: " + e.ToString()); + success = false; + } + } + return success; + } + + [Obsolete] + public List SaveData(ref bool success) { + return null; + } + + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading {data.Count} priority segments (new method)"); + foreach (var prioSegData in data) { + try { + if ((PriorityType)prioSegData.priorityType == PriorityType.None) { + continue; + } + if (!Services.NetService.IsNodeValid(prioSegData.nodeId)) { + continue; + } + if (!Services.NetService.IsSegmentValid(prioSegData.segmentId)) { + continue; + } + bool? startNode = Services.NetService.IsStartNode(prioSegData.segmentId, prioSegData.nodeId); + if (startNode == null) { + Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {prioSegData.nodeId} @ seg. {prioSegData.segmentId}"); + continue; + } #if DEBUGLOAD Log._Debug($"Loading priority sign {(PriorityType)prioSegData.priorityType} @ seg. {prioSegData.segmentId}, start node? {startNode}"); #endif - SetPrioritySign(prioSegData.segmentId, (bool)startNode, (PriorityType)prioSegData.priorityType); - } catch (Exception e) { - // ignore, as it's probably corrupt save data. it'll be culled on next save - Log.Warning("Error loading data from Priority segments: " + e.ToString()); - success = false; - } - } - return success; - } - - List ICustomDataManager>.SaveData(ref bool success) { - List ret = new List(); - for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { - try { - if (! Services.NetService.IsSegmentValid((ushort)segmentId) || ! HasSegmentPrioritySign((ushort)segmentId)) { - continue; - } - - PriorityType startSign = GetPrioritySign((ushort)segmentId, true); - if (startSign != PriorityType.None) { - ushort startNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, true); - if (Services.NetService.IsNodeValid(startNodeId)) { + SetPrioritySign(prioSegData.segmentId, (bool)startNode, (PriorityType)prioSegData.priorityType); + } catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Warning("Error loading data from Priority segments: " + e.ToString()); + success = false; + } + } + return success; + } + + List ICustomDataManager>.SaveData(ref bool success) { + List ret = new List(); + for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { + try { + if (! Services.NetService.IsSegmentValid((ushort)segmentId) || ! HasSegmentPrioritySign((ushort)segmentId)) { + continue; + } + + PriorityType startSign = GetPrioritySign((ushort)segmentId, true); + if (startSign != PriorityType.None) { + ushort startNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, true); + if (Services.NetService.IsNodeValid(startNodeId)) { #if DEBUGSAVE Log._Debug($"Saving priority sign of type {startSign} @ start node {startNodeId} of segment {segmentId}"); #endif - ret.Add(new Configuration.PrioritySegment((ushort)segmentId, startNodeId, (int)startSign)); - } - } + ret.Add(new Configuration.PrioritySegment((ushort)segmentId, startNodeId, (int)startSign)); + } + } - PriorityType endSign = GetPrioritySign((ushort)segmentId, false); - if (endSign != PriorityType.None) { - ushort endNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, false); - if (Services.NetService.IsNodeValid(endNodeId)) { + PriorityType endSign = GetPrioritySign((ushort)segmentId, false); + if (endSign != PriorityType.None) { + ushort endNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, false); + if (Services.NetService.IsNodeValid(endNodeId)) { #if DEBUGSAVE Log._Debug($"Saving priority sign of type {endSign} @ end node {endNodeId} of segment {segmentId}"); #endif - ret.Add(new Configuration.PrioritySegment((ushort)segmentId, endNodeId, (int)endSign)); - } - } - } catch (Exception e) { - Log.Error($"Exception occurred while saving priority segment @ seg. {segmentId}: {e.ToString()}"); - success = false; - } - } - return ret; - } - } -} + ret.Add(new Configuration.PrioritySegment((ushort)segmentId, endNodeId, (int)endSign)); + } + } + } catch (Exception e) { + Log.Error($"Exception occurred while saving priority segment @ seg. {segmentId}: {e.ToString()}"); + success = false; + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs index c5dc0e152..dfb4f0826 100644 --- a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs @@ -1,876 +1,866 @@ -using ColossalFramework; -using ColossalFramework.Math; -using CSUtil.Commons; -using CSUtil.Commons.Benchmark; -using GenericGameBridge.Service; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Custom.AI; -using TrafficManager.Custom.PathFinding; -using TrafficManager.Geometry.Impl; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using TrafficManager.UI; -using TrafficManager.Util; -using UnityEngine; -using static TrafficManager.Traffic.Data.PrioritySegment; - -namespace TrafficManager.Manager.Impl { - public class VehicleBehaviorManager : AbstractCustomManager, IVehicleBehaviorManager { - public const float MIN_SPEED = 8f * 0.2f; // 10 km/h - public const float MAX_EVASION_SPEED = 8f * 1f; // 50 km/h - public const float EVASION_SPEED = 8f * 0.2f; // 10 km/h - public const float ICY_ROADS_MIN_SPEED = 8f * 0.4f; // 20 km/h - public const float ICY_ROADS_STUDDED_MIN_SPEED = 8f * 0.8f; // 40 km/h - public const float WET_ROADS_MAX_SPEED = 8f * 2f; // 100 km/h - public const float WET_ROADS_FACTOR = 0.75f; - public const float BROKEN_ROADS_MAX_SPEED = 8f * 1.6f; // 80 km/h - public const float BROKEN_ROADS_FACTOR = 0.75f; - - public const VehicleInfo.VehicleType RECKLESS_VEHICLE_TYPES = VehicleInfo.VehicleType.Car; - - private static PathUnit.Position DUMMY_POS = default(PathUnit.Position); - private static readonly uint[] POW2MASKS = new uint[] { - 1u, 2u, 4u, 8u, - 16u, 32u, 64u, 128u, - 256u, 512u, 1024u, 2048u, - 4096u, 8192u, 16384u, 32768u, - 65536u, 131072u, 262144u, 524288u, - 1048576u, 2097152u, 4194304u, 8388608u, - 16777216u, 33554432u, 67108864u, 134217728u, - 268435456u, 536870912u, 1073741824u, 2147483648u - }; - - public static readonly VehicleBehaviorManager Instance = new VehicleBehaviorManager(); - - private VehicleBehaviorManager() { - - } - - public bool ParkPassengerCar(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, uint driverCitizenId, ref Citizen driverCitizen, ushort driverCitizenInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ushort targetBuildingId, PathUnit.Position pathPos, uint nextPath, int nextPositionIndex, out byte segmentOffset) { -#if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID) && - (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; -#endif - IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; - PathManager pathManager = Singleton.instance; - CitizenManager citizenManager = Singleton.instance; - NetManager netManager = Singleton.instance; - VehicleManager vehicleManager = Singleton.instance; - - // NON-STOCK CODE START - bool prohibitPocketCars = false; - // NON-STOCK CODE END - - if (driverCitizenId != 0u) { - if (Options.parkingAI && driverCitizenInstanceId != 0) { - prohibitPocketCars = true; - } - - uint laneID = PathManager.GetLaneID(pathPos); - segmentOffset = (byte)Singleton.instance.m_randomizer.Int32(1, 254); - Vector3 refPos; - Vector3 vector; - netManager.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)segmentOffset * 0.003921569f, out refPos, out vector); - NetInfo info = netManager.m_segments.m_buffer[(int)pathPos.m_segment].Info; - bool isSegmentInverted = (netManager.m_segments.m_buffer[(int)pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None; - bool isPosNegative = info.m_lanes[(int)pathPos.m_lane].m_position < 0f; - vector.Normalize(); - Vector3 searchDir; - if (isSegmentInverted != isPosNegative) { - searchDir.x = -vector.z; - searchDir.y = 0f; - searchDir.z = vector.x; - } else { - searchDir.x = vector.z; - searchDir.y = 0f; - searchDir.z = -vector.x; - } - ushort homeID = 0; - if (driverCitizenId != 0u) { - homeID = driverCitizen.m_homeBuilding; - } - Vector3 parkPos = default(Vector3); - Quaternion parkRot = default(Quaternion); - float parkOffset = -1f; - - // NON-STOCK CODE START - bool foundParkingSpace = false; - bool searchedParkingSpace = false; - - if (prohibitPocketCars) { -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} tries to park on a parking position now (flags: {vehicleData.m_flags})! CurrentPathMode={driverExtInstance.pathMode} path={vehicleData.m_path} pathPositionIndex={vehicleData.m_pathPositionIndex} segmentId={pathPos.m_segment} laneIndex={pathPos.m_lane} offset={pathPos.m_offset} nextPath={nextPath} refPos={refPos} searchDir={searchDir} home={homeID} driverCitizenId={driverCitizenId} driverCitizenInstanceId={driverCitizenInstanceId}"); -#endif - - if (driverExtInstance.pathMode == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) { - // try to use previously found parking space -#if DEBUG - if (debug) - Log._Debug($"Vehicle {vehicleID} tries to park on an (alternative) parking position now! CurrentPathMode={driverExtInstance.pathMode} altParkingSpaceLocation={driverExtInstance.parkingSpaceLocation} altParkingSpaceLocationId={driverExtInstance.parkingSpaceLocationId}"); -#endif - - searchedParkingSpace = true; - switch (driverExtInstance.parkingSpaceLocation) { - case ExtParkingSpaceLocation.RoadSide: - uint parkLaneID; - int parkLaneIndex; -#if DEBUG - if (debug) - Log._Debug($"Vehicle {vehicleID} wants to park road-side @ segment {driverExtInstance.parkingSpaceLocationId}"); -#endif - foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpaceRoadSideForVehiclePos(vehicleInfo, 0, driverExtInstance.parkingSpaceLocationId, refPos, out parkPos, out parkRot, out parkOffset, out parkLaneID, out parkLaneIndex); - break; - case ExtParkingSpaceLocation.Building: - float maxDist = 9999f; -#if DEBUG - if (debug) - Log._Debug($"Vehicle {vehicleID} wants to park @ building {driverExtInstance.parkingSpaceLocationId}"); -#endif - foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpacePropAtBuilding(vehicleInfo, homeID, 0, driverExtInstance.parkingSpaceLocationId, ref Singleton.instance.m_buildings.m_buffer[driverExtInstance.parkingSpaceLocationId], pathPos.m_segment, refPos, ref maxDist, true, out parkPos, out parkRot, out parkOffset); - break; - default: -#if DEBUG - Log.Error($"No alternative parking position stored for vehicle {vehicleID}! PathMode={driverExtInstance.pathMode}"); -#endif - ExtParkingSpaceLocation parkLoc; - ushort parkId; - foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, out parkPos, out parkRot, out parkOffset); - break; - } - } - } - - if (!searchedParkingSpace) { - ExtParkingSpaceLocation parkLoc; - ushort parkId; - foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, out parkPos, out parkRot, out parkOffset); -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Found parking space? {foundParkingSpace}. parkPos={parkPos}, parkRot={parkRot}, parkOffset={parkOffset}"); -#endif - } - - // NON-STOCK CODE END - ushort parkedVehicleId = 0; - bool parkedCarCreated = foundParkingSpace && vehicleManager.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, driverCitizenId); -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parked car created? {parkedCarCreated}"); -#endif - - IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; - if (foundParkingSpace && parkedCarCreated) { - // we have reached a parking position -#if DEBUG - float sqrDist = (refPos - parkPos).sqrMagnitude; - if (fineDebug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} succeeded in parking! CurrentPathMode={driverExtInstance.pathMode} sqrDist={sqrDist}"); -#endif - - driverCitizen.SetParkedVehicle(driverCitizenId, parkedVehicleId); - if (parkOffset >= 0f) { - segmentOffset = (byte)(parkOffset * 255f); - } - - // NON-STOCK CODE START - if (prohibitPocketCars) { - if ((driverExtInstance.pathMode == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { - // decrease parking space demand of target building - Constants.ManagerFactory.ExtBuildingManager.ModifyParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], parkPos, GlobalConfig.Instance.ParkingAI.MinFoundParkPosParkingSpaceDemandDelta, GlobalConfig.Instance.ParkingAI.MaxFoundParkPosParkingSpaceDemandDelta); - } - - //if (driverExtInstance.CurrentPathMode == ExtCitizenInstance.PathMode.DrivingToAltParkPos || driverExtInstance.CurrentPathMode == ExtCitizenInstance.PathMode.DrivingToKnownParkPos) { - // we have reached an (alternative) parking position and succeeded in finding a parking space - driverExtInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; - driverExtInstance.failedParkingAttempts = 0; - driverExtInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; - driverExtInstance.parkingSpaceLocationId = 0; -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} has reached an (alternative) parking position! CurrentPathMode={driverExtInstance.pathMode} position={parkPos}"); -#endif - //} - } - } else if (prohibitPocketCars) { - // could not find parking space. vehicle would despawn. - if ( - targetBuildingId != 0 && - (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None && - (refPos - Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_position).magnitude <= GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance - ) { - // vehicle is at target and target is an outside connection: accept despawn -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Driver citizen instance {driverCitizenInstanceId} wants to park at an outside connection. Aborting."); -#endif - return true; - } - - // Find parking space in the vicinity, redo path-finding to the parking space, park the vehicle and do citizen path-finding to the current target - - if (!foundParkingSpace && (driverExtInstance.pathMode == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { - // increase parking space demand of target building - if (driverExtInstance.failedParkingAttempts > 1) { - extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], GlobalConfig.Instance.ParkingAI.FailedParkingSpaceDemandIncrement * (uint)(driverExtInstance.failedParkingAttempts - 1)); - } - } - - if (!foundParkingSpace) { -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}: Could not find parking space. ABORT."); -#endif - ++driverExtInstance.failedParkingAttempts; - } else { -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}: Parked car could not be created. ABORT."); -#endif - driverExtInstance.failedParkingAttempts = GlobalConfig.Instance.ParkingAI.MaxParkingAttempts + 1; - } - driverExtInstance.pathMode = ExtPathMode.ParkingFailed; - driverExtInstance.parkingPathStartPosition = pathPos; - -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}! (flags: {vehicleData.m_flags}) pathPos segment={pathPos.m_segment}, lane={pathPos.m_lane}, offset={pathPos.m_offset}. Trying to find parking space in the vicinity. FailedParkingAttempts={driverExtInstance.failedParkingAttempts}, CurrentPathMode={driverExtInstance.pathMode} foundParkingSpace={foundParkingSpace}"); -#endif - - // invalidate paths of all passengers in order to force path recalculation - uint curUnitId = vehicleData.m_citizenUnits; - int numIter = 0; - while (curUnitId != 0u) { - uint nextUnit = citizenManager.m_units.m_buffer[curUnitId].m_nextUnit; - for (int i = 0; i < 5; i++) { - uint curCitizenId = citizenManager.m_units.m_buffer[curUnitId].GetCitizen(i); - if (curCitizenId != 0u) { - ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[curCitizenId].m_instance; - if (citizenInstanceId != 0) { - -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Releasing path for citizen instance {citizenInstanceId} sitting in vehicle {vehicleID} (was {citizenManager.m_instances.m_buffer[citizenInstanceId].m_path})."); -#endif - if (citizenInstanceId != driverCitizenInstanceId) { -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Resetting pathmode for passenger citizen instance {citizenInstanceId} sitting in vehicle {vehicleID} (was {ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].pathMode})."); -#endif - - extCitizenInstanceManager.Reset(ref extCitizenInstanceManager.ExtInstances[citizenInstanceId]); - } - - if (citizenManager.m_instances.m_buffer[citizenInstanceId].m_path != 0) { - Singleton.instance.ReleasePath(citizenManager.m_instances.m_buffer[citizenInstanceId].m_path); - citizenManager.m_instances.m_buffer[citizenInstanceId].m_path = 0u; - } - } - } - } - curUnitId = nextUnit; - if (++numIter > CitizenManager.MAX_UNIT_COUNT) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - return false; - // NON-STOCK CODE END - } - } else { - segmentOffset = pathPos.m_offset; - } - - // parking has succeeded - if (driverCitizenId != 0u) { - uint curCitizenUnitId = vehicleData.m_citizenUnits; - int numIter = 0; - while (curCitizenUnitId != 0u) { - uint nextUnit = citizenManager.m_units.m_buffer[curCitizenUnitId].m_nextUnit; - for (int j = 0; j < 5; j++) { - uint citId = citizenManager.m_units.m_buffer[curCitizenUnitId].GetCitizen(j); - if (citId != 0u) { - ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[citId].m_instance; - if (citizenInstanceId != 0) { - // NON-STOCK CODE START - if (prohibitPocketCars) { - if (driverExtInstance.pathMode == ExtPathMode.RequiresWalkingPathToTarget) { -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded: Doing nothing for citizen instance {citizenInstanceId}! path: {citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path}"); -#endif - extCitizenInstanceManager.ExtInstances[citizenInstanceId].pathMode = ExtPathMode.RequiresWalkingPathToTarget; - continue; - } - } - // NON-STOCK CODE END - - if (pathManager.AddPathReference(nextPath)) { - if (citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path != 0u) { - pathManager.ReleasePath(citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path); - } - citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path = nextPath; - citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_pathPositionIndex = (byte)nextPositionIndex; - citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_lastPathOffset = segmentOffset; -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded (default): Setting path of citizen instance {citizenInstanceId} to {nextPath}!"); -#endif - } - } - } - } - curCitizenUnitId = nextUnit; - if (++numIter > 524288) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } - - if (prohibitPocketCars) { - if (driverExtInstance.pathMode == ExtPathMode.RequiresWalkingPathToTarget) { -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded (alternative parking spot): Citizen instance {driverExtInstance} has to walk for the remaining path!"); -#endif - /*driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.CalculatingWalkingPathToTarget; - if (debug) - Log._Debug($"Setting CurrentPathMode of vehicle {vehicleID} to {driverExtInstance.CurrentPathMode}");*/ - } - } - - return true; - } - - public bool StartPassengerCarPathFind(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, ushort driverInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget, bool isHeavyVehicle, bool hasCombustionEngine, bool ignoreBlocked) { - IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; -#if DEBUG - bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID) && - (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && - (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) && - (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) && - (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding) - ; - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; - - if (debug) - Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): called for vehicle {vehicleID}, driverInstanceId={driverInstanceId}, startPos={startPos}, endPos={endPos}, sourceBuilding={vehicleData.m_sourceBuilding}, targetBuilding={vehicleData.m_targetBuilding} pathMode={driverExtInstance.pathMode}"); -#endif - - PathUnit.Position startPosA = default(PathUnit.Position); - PathUnit.Position startPosB = default(PathUnit.Position); - PathUnit.Position endPosA = default(PathUnit.Position); - float sqrDistA = 0f; - float sqrDistB; - - ushort targetBuildingId = driverInstance.m_targetBuilding; - uint driverCitizenId = driverInstance.m_citizen; - - // NON-STOCK CODE START - bool calculateEndPos = true; - bool allowRandomParking = true; - bool movingToParkingPos = false; - bool foundStartingPos = false; - bool skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - ExtPathType extPathType = ExtPathType.None; +namespace TrafficManager.Manager.Impl { + using System; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Custom.PathFinding; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + + public class VehicleBehaviorManager : AbstractCustomManager, IVehicleBehaviorManager { + public const float MIN_SPEED = 8f * 0.2f; // 10 km/h + public const float MAX_EVASION_SPEED = 8f * 1f; // 50 km/h + public const float EVASION_SPEED = 8f * 0.2f; // 10 km/h + public const float ICY_ROADS_MIN_SPEED = 8f * 0.4f; // 20 km/h + public const float ICY_ROADS_STUDDED_MIN_SPEED = 8f * 0.8f; // 40 km/h + public const float WET_ROADS_MAX_SPEED = 8f * 2f; // 100 km/h + public const float WET_ROADS_FACTOR = 0.75f; + public const float BROKEN_ROADS_MAX_SPEED = 8f * 1.6f; // 80 km/h + public const float BROKEN_ROADS_FACTOR = 0.75f; + + public const VehicleInfo.VehicleType RECKLESS_VEHICLE_TYPES = VehicleInfo.VehicleType.Car; + + private static PathUnit.Position DUMMY_POS = default(PathUnit.Position); + private static readonly uint[] POW2MASKS = { + 1u, 2u, 4u, 8u, + 16u, 32u, 64u, 128u, + 256u, 512u, 1024u, 2048u, + 4096u, 8192u, 16384u, 32768u, + 65536u, 131072u, 262144u, 524288u, + 1048576u, 2097152u, 4194304u, 8388608u, + 16777216u, 33554432u, 67108864u, 134217728u, + 268435456u, 536870912u, 1073741824u, 2147483648u + }; + + public static readonly VehicleBehaviorManager Instance = new VehicleBehaviorManager(); + + private VehicleBehaviorManager() { + + } + + public bool ParkPassengerCar(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, uint driverCitizenId, ref Citizen driverCitizen, ushort driverCitizenInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ushort targetBuildingId, PathUnit.Position pathPos, uint nextPath, int nextPositionIndex, out byte segmentOffset) { +#if DEBUG + bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID) && + (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; +#endif + IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; + PathManager pathManager = Singleton.instance; + CitizenManager citizenManager = Singleton.instance; + NetManager netManager = Singleton.instance; + VehicleManager vehicleManager = Singleton.instance; + + // NON-STOCK CODE START + bool prohibitPocketCars = false; + // NON-STOCK CODE END + + if (driverCitizenId != 0u) { + if (Options.parkingAI && driverCitizenInstanceId != 0) { + prohibitPocketCars = true; + } + + uint laneID = PathManager.GetLaneID(pathPos); + segmentOffset = (byte)Singleton.instance.m_randomizer.Int32(1, 254); + Vector3 refPos; + Vector3 vector; + netManager.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)segmentOffset * 0.003921569f, out refPos, out vector); + NetInfo info = netManager.m_segments.m_buffer[(int)pathPos.m_segment].Info; + bool isSegmentInverted = (netManager.m_segments.m_buffer[(int)pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None; + bool isPosNegative = info.m_lanes[(int)pathPos.m_lane].m_position < 0f; + vector.Normalize(); + Vector3 searchDir; + if (isSegmentInverted != isPosNegative) { + searchDir.x = -vector.z; + searchDir.y = 0f; + searchDir.z = vector.x; + } else { + searchDir.x = vector.z; + searchDir.y = 0f; + searchDir.z = -vector.x; + } + ushort homeID = 0; + if (driverCitizenId != 0u) { + homeID = driverCitizen.m_homeBuilding; + } + Vector3 parkPos = default(Vector3); + Quaternion parkRot = default(Quaternion); + float parkOffset = -1f; + + // NON-STOCK CODE START + bool foundParkingSpace = false; + bool searchedParkingSpace = false; + + if (prohibitPocketCars) { +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} tries to park on a parking position now (flags: {vehicleData.m_flags})! CurrentPathMode={driverExtInstance.pathMode} path={vehicleData.m_path} pathPositionIndex={vehicleData.m_pathPositionIndex} segmentId={pathPos.m_segment} laneIndex={pathPos.m_lane} offset={pathPos.m_offset} nextPath={nextPath} refPos={refPos} searchDir={searchDir} home={homeID} driverCitizenId={driverCitizenId} driverCitizenInstanceId={driverCitizenInstanceId}"); +#endif + + if (driverExtInstance.pathMode == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) { + // try to use previously found parking space +#if DEBUG + if (debug) + Log._Debug($"Vehicle {vehicleID} tries to park on an (alternative) parking position now! CurrentPathMode={driverExtInstance.pathMode} altParkingSpaceLocation={driverExtInstance.parkingSpaceLocation} altParkingSpaceLocationId={driverExtInstance.parkingSpaceLocationId}"); +#endif + + searchedParkingSpace = true; + switch (driverExtInstance.parkingSpaceLocation) { + case ExtParkingSpaceLocation.RoadSide: + uint parkLaneID; + int parkLaneIndex; +#if DEBUG + if (debug) + Log._Debug($"Vehicle {vehicleID} wants to park road-side @ segment {driverExtInstance.parkingSpaceLocationId}"); +#endif + foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpaceRoadSideForVehiclePos(vehicleInfo, 0, driverExtInstance.parkingSpaceLocationId, refPos, out parkPos, out parkRot, out parkOffset, out parkLaneID, out parkLaneIndex); + break; + case ExtParkingSpaceLocation.Building: + float maxDist = 9999f; +#if DEBUG + if (debug) + Log._Debug($"Vehicle {vehicleID} wants to park @ building {driverExtInstance.parkingSpaceLocationId}"); +#endif + foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpacePropAtBuilding(vehicleInfo, homeID, 0, driverExtInstance.parkingSpaceLocationId, ref Singleton.instance.m_buildings.m_buffer[driverExtInstance.parkingSpaceLocationId], pathPos.m_segment, refPos, ref maxDist, true, out parkPos, out parkRot, out parkOffset); + break; + default: +#if DEBUG + Log.Error($"No alternative parking position stored for vehicle {vehicleID}! PathMode={driverExtInstance.pathMode}"); +#endif + ExtParkingSpaceLocation parkLoc; + ushort parkId; + foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, out parkPos, out parkRot, out parkOffset); + break; + } + } + } + + if (!searchedParkingSpace) { + ExtParkingSpaceLocation parkLoc; + ushort parkId; + foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, out parkPos, out parkRot, out parkOffset); +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Found parking space? {foundParkingSpace}. parkPos={parkPos}, parkRot={parkRot}, parkOffset={parkOffset}"); +#endif + } + + // NON-STOCK CODE END + ushort parkedVehicleId = 0; + bool parkedCarCreated = foundParkingSpace && vehicleManager.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, driverCitizenId); +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parked car created? {parkedCarCreated}"); +#endif + + IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; + if (foundParkingSpace && parkedCarCreated) { + // we have reached a parking position +#if DEBUG + float sqrDist = (refPos - parkPos).sqrMagnitude; + if (fineDebug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} succeeded in parking! CurrentPathMode={driverExtInstance.pathMode} sqrDist={sqrDist}"); +#endif + + driverCitizen.SetParkedVehicle(driverCitizenId, parkedVehicleId); + if (parkOffset >= 0f) { + segmentOffset = (byte)(parkOffset * 255f); + } + + // NON-STOCK CODE START + if (prohibitPocketCars) { + if ((driverExtInstance.pathMode == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { + // decrease parking space demand of target building + Constants.ManagerFactory.ExtBuildingManager.ModifyParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], parkPos, GlobalConfig.Instance.ParkingAI.MinFoundParkPosParkingSpaceDemandDelta, GlobalConfig.Instance.ParkingAI.MaxFoundParkPosParkingSpaceDemandDelta); + } + + //if (driverExtInstance.CurrentPathMode == ExtCitizenInstance.PathMode.DrivingToAltParkPos || driverExtInstance.CurrentPathMode == ExtCitizenInstance.PathMode.DrivingToKnownParkPos) { + // we have reached an (alternative) parking position and succeeded in finding a parking space + driverExtInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; + driverExtInstance.failedParkingAttempts = 0; + driverExtInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; + driverExtInstance.parkingSpaceLocationId = 0; +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} has reached an (alternative) parking position! CurrentPathMode={driverExtInstance.pathMode} position={parkPos}"); +#endif + //} + } + } else if (prohibitPocketCars) { + // could not find parking space. vehicle would despawn. + if ( + targetBuildingId != 0 && + (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None && + (refPos - Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_position).magnitude <= GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance + ) { + // vehicle is at target and target is an outside connection: accept despawn +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Driver citizen instance {driverCitizenInstanceId} wants to park at an outside connection. Aborting."); +#endif + return true; + } + + // Find parking space in the vicinity, redo path-finding to the parking space, park the vehicle and do citizen path-finding to the current target + + if (!foundParkingSpace && (driverExtInstance.pathMode == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { + // increase parking space demand of target building + if (driverExtInstance.failedParkingAttempts > 1) { + extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], GlobalConfig.Instance.ParkingAI.FailedParkingSpaceDemandIncrement * (uint)(driverExtInstance.failedParkingAttempts - 1)); + } + } + + if (!foundParkingSpace) { +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}: Could not find parking space. ABORT."); +#endif + ++driverExtInstance.failedParkingAttempts; + } else { +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}: Parked car could not be created. ABORT."); +#endif + driverExtInstance.failedParkingAttempts = GlobalConfig.Instance.ParkingAI.MaxParkingAttempts + 1; + } + driverExtInstance.pathMode = ExtPathMode.ParkingFailed; + driverExtInstance.parkingPathStartPosition = pathPos; + +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}! (flags: {vehicleData.m_flags}) pathPos segment={pathPos.m_segment}, lane={pathPos.m_lane}, offset={pathPos.m_offset}. Trying to find parking space in the vicinity. FailedParkingAttempts={driverExtInstance.failedParkingAttempts}, CurrentPathMode={driverExtInstance.pathMode} foundParkingSpace={foundParkingSpace}"); +#endif + + // invalidate paths of all passengers in order to force path recalculation + uint curUnitId = vehicleData.m_citizenUnits; + int numIter = 0; + while (curUnitId != 0u) { + uint nextUnit = citizenManager.m_units.m_buffer[curUnitId].m_nextUnit; + for (int i = 0; i < 5; i++) { + uint curCitizenId = citizenManager.m_units.m_buffer[curUnitId].GetCitizen(i); + if (curCitizenId != 0u) { + ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[curCitizenId].m_instance; + if (citizenInstanceId != 0) { + +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Releasing path for citizen instance {citizenInstanceId} sitting in vehicle {vehicleID} (was {citizenManager.m_instances.m_buffer[citizenInstanceId].m_path})."); +#endif + if (citizenInstanceId != driverCitizenInstanceId) { +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Resetting pathmode for passenger citizen instance {citizenInstanceId} sitting in vehicle {vehicleID} (was {ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].pathMode})."); +#endif + + extCitizenInstanceManager.Reset(ref extCitizenInstanceManager.ExtInstances[citizenInstanceId]); + } + + if (citizenManager.m_instances.m_buffer[citizenInstanceId].m_path != 0) { + Singleton.instance.ReleasePath(citizenManager.m_instances.m_buffer[citizenInstanceId].m_path); + citizenManager.m_instances.m_buffer[citizenInstanceId].m_path = 0u; + } + } + } + } + curUnitId = nextUnit; + if (++numIter > CitizenManager.MAX_UNIT_COUNT) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + return false; + // NON-STOCK CODE END + } + } else { + segmentOffset = pathPos.m_offset; + } + + // parking has succeeded + if (driverCitizenId != 0u) { + uint curCitizenUnitId = vehicleData.m_citizenUnits; + int numIter = 0; + while (curCitizenUnitId != 0u) { + uint nextUnit = citizenManager.m_units.m_buffer[curCitizenUnitId].m_nextUnit; + for (int j = 0; j < 5; j++) { + uint citId = citizenManager.m_units.m_buffer[curCitizenUnitId].GetCitizen(j); + if (citId != 0u) { + ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[citId].m_instance; + if (citizenInstanceId != 0) { + // NON-STOCK CODE START + if (prohibitPocketCars) { + if (driverExtInstance.pathMode == ExtPathMode.RequiresWalkingPathToTarget) { +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded: Doing nothing for citizen instance {citizenInstanceId}! path: {citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path}"); +#endif + extCitizenInstanceManager.ExtInstances[citizenInstanceId].pathMode = ExtPathMode.RequiresWalkingPathToTarget; + continue; + } + } + // NON-STOCK CODE END + + if (pathManager.AddPathReference(nextPath)) { + if (citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path != 0u) { + pathManager.ReleasePath(citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path); + } + citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path = nextPath; + citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_pathPositionIndex = (byte)nextPositionIndex; + citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_lastPathOffset = segmentOffset; +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded (default): Setting path of citizen instance {citizenInstanceId} to {nextPath}!"); +#endif + } + } + } + } + curCitizenUnitId = nextUnit; + if (++numIter > 524288) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } + + if (prohibitPocketCars) { + if (driverExtInstance.pathMode == ExtPathMode.RequiresWalkingPathToTarget) { +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded (alternative parking spot): Citizen instance {driverExtInstance} has to walk for the remaining path!"); +#endif + /*driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.CalculatingWalkingPathToTarget; + if (debug) + Log._Debug($"Setting CurrentPathMode of vehicle {vehicleID} to {driverExtInstance.CurrentPathMode}");*/ + } + } + + return true; + } + + public bool StartPassengerCarPathFind(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, ushort driverInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget, bool isHeavyVehicle, bool hasCombustionEngine, bool ignoreBlocked) { + IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; +#if DEBUG + bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID) && + (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) && + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) && + (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) && + (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding) + ; + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + + if (debug) + Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): called for vehicle {vehicleID}, driverInstanceId={driverInstanceId}, startPos={startPos}, endPos={endPos}, sourceBuilding={vehicleData.m_sourceBuilding}, targetBuilding={vehicleData.m_targetBuilding} pathMode={driverExtInstance.pathMode}"); +#endif + + PathUnit.Position startPosA = default(PathUnit.Position); + PathUnit.Position startPosB = default(PathUnit.Position); + PathUnit.Position endPosA = default(PathUnit.Position); + float sqrDistA = 0f; + float sqrDistB; + + ushort targetBuildingId = driverInstance.m_targetBuilding; + uint driverCitizenId = driverInstance.m_citizen; + + // NON-STOCK CODE START + bool calculateEndPos = true; + bool allowRandomParking = true; + bool movingToParkingPos = false; + bool foundStartingPos = false; + bool skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + ExtPathType extPathType = ExtPathType.None; #if BENCHMARK using (var bm = new Benchmark(null, "ParkingAI")) { #endif - if (Options.parkingAI) { - //if (driverExtInstance != null) { + if (Options.parkingAI) { + //if (driverExtInstance != null) { #if DEBUG - if (debug) - Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode={driverExtInstance.pathMode} for vehicle {vehicleID}, driver citizen instance {driverExtInstance.instanceId}!"); + if (debug) + Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode={driverExtInstance.pathMode} for vehicle {vehicleID}, driver citizen instance {driverExtInstance.instanceId}!"); #endif - if (driverExtInstance.pathMode == ExtPathMode.RequiresMixedCarPathToTarget) { - driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; - startBothWays = false; + if (driverExtInstance.pathMode == ExtPathMode.RequiresMixedCarPathToTarget) { + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; + startBothWays = false; #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was RequiresDirectCarPathToTarget: Parking spaces will NOT be searched beforehand. Setting pathMode={driverExtInstance.pathMode}"); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was RequiresDirectCarPathToTarget: Parking spaces will NOT be searched beforehand. Setting pathMode={driverExtInstance.pathMode}"); #endif - } else if ( - driverExtInstance.pathMode != ExtPathMode.ParkingFailed && - targetBuildingId != 0 && - (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None - ) { - // target is outside connection - driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; + } else if ( + driverExtInstance.pathMode != ExtPathMode.ParkingFailed && + targetBuildingId != 0 && + (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None + ) { + // target is outside connection + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was not ParkingFailed and target is outside connection: Setting pathMode={driverExtInstance.pathMode}"); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was not ParkingFailed and target is outside connection: Setting pathMode={driverExtInstance.pathMode}"); #endif - } else { - if (driverExtInstance.pathMode == ExtPathMode.DrivingToTarget || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos || driverExtInstance.pathMode == ExtPathMode.ParkingFailed) { + } else { + if (driverExtInstance.pathMode == ExtPathMode.DrivingToTarget || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos || driverExtInstance.pathMode == ExtPathMode.ParkingFailed) { #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Skipping queue. pathMode={driverExtInstance.pathMode}"); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Skipping queue. pathMode={driverExtInstance.pathMode}"); #endif - skipQueue = true; - } + skipQueue = true; + } - bool allowTourists = false; - bool searchAtCurrentPos = false; - if (driverExtInstance.pathMode == ExtPathMode.ParkingFailed) { - // previous parking attempt failed - driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToAltParkPos; - allowTourists = true; - searchAtCurrentPos = true; + bool allowTourists = false; + bool searchAtCurrentPos = false; + if (driverExtInstance.pathMode == ExtPathMode.ParkingFailed) { + // previous parking attempt failed + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToAltParkPos; + allowTourists = true; + searchAtCurrentPos = true; #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Vehicle {vehicleID} shall move to an alternative parking position! CurrentPathMode={driverExtInstance.pathMode} FailedParkingAttempts={driverExtInstance.failedParkingAttempts}"); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Vehicle {vehicleID} shall move to an alternative parking position! CurrentPathMode={driverExtInstance.pathMode} FailedParkingAttempts={driverExtInstance.failedParkingAttempts}"); #endif - if (driverExtInstance.parkingPathStartPosition != null) { - startPosA = (PathUnit.Position)driverExtInstance.parkingPathStartPosition; - foundStartingPos = true; + if (driverExtInstance.parkingPathStartPosition != null) { + startPosA = (PathUnit.Position)driverExtInstance.parkingPathStartPosition; + foundStartingPos = true; #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Setting starting pos for {vehicleID} to segment={startPosA.m_segment}, laneIndex={startPosA.m_lane}, offset={startPosA.m_offset}"); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Setting starting pos for {vehicleID} to segment={startPosA.m_segment}, laneIndex={startPosA.m_lane}, offset={startPosA.m_offset}"); #endif - } - startBothWays = false; + } + startBothWays = false; - if (driverExtInstance.failedParkingAttempts > GlobalConfig.Instance.ParkingAI.MaxParkingAttempts) { - // maximum number of parking attempts reached + if (driverExtInstance.failedParkingAttempts > GlobalConfig.Instance.ParkingAI.MaxParkingAttempts) { + // maximum number of parking attempts reached #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Reached maximum number of parking attempts for vehicle {vehicleID}! GIVING UP."); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Reached maximum number of parking attempts for vehicle {vehicleID}! GIVING UP."); #endif - extCitizenInstanceManager.Reset(ref driverExtInstance); + extCitizenInstanceManager.Reset(ref driverExtInstance); - // pocket car fallback - //vehicleData.m_flags |= Vehicle.Flags.Parking; - return false; - } else { + // pocket car fallback + //vehicleData.m_flags |= Vehicle.Flags.Parking; + return false; + } else { #if DEBUG - if (fineDebug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Increased number of parking attempts for vehicle {vehicleID}: {driverExtInstance.failedParkingAttempts}/{GlobalConfig.Instance.ParkingAI.MaxParkingAttempts}"); + if (fineDebug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Increased number of parking attempts for vehicle {vehicleID}: {driverExtInstance.failedParkingAttempts}/{GlobalConfig.Instance.ParkingAI.MaxParkingAttempts}"); #endif - } - } else { - driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToKnownParkPos; + } + } else { + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToKnownParkPos; #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No parking involved: Setting pathMode={driverExtInstance.pathMode}"); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No parking involved: Setting pathMode={driverExtInstance.pathMode}"); #endif - } + } - ushort homeId = Singleton.instance.m_citizens.m_buffer[driverCitizenId].m_homeBuilding; - bool calcEndPos; - Vector3 parkPos; + ushort homeId = Singleton.instance.m_citizens.m_buffer[driverCitizenId].m_homeBuilding; + bool calcEndPos; + Vector3 parkPos; - Vector3 returnPos = searchAtCurrentPos ? (Vector3)vehicleData.m_targetPos3 : endPos; - if (AdvancedParkingManager.Instance.FindParkingSpaceForCitizen(returnPos, vehicleData.Info, ref driverExtInstance, homeId, targetBuildingId == homeId, vehicleID, allowTourists, out parkPos, ref endPosA, out calcEndPos)) { - calculateEndPos = calcEndPos; - allowRandomParking = false; - movingToParkingPos = true; + Vector3 returnPos = searchAtCurrentPos ? (Vector3)vehicleData.m_targetPos3 : endPos; + if (AdvancedParkingManager.Instance.FindParkingSpaceForCitizen(returnPos, vehicleData.Info, ref driverExtInstance, homeId, targetBuildingId == homeId, vehicleID, allowTourists, out parkPos, ref endPosA, out calcEndPos)) { + calculateEndPos = calcEndPos; + allowRandomParking = false; + movingToParkingPos = true; - if (!extCitizenInstanceManager.CalculateReturnPath(ref driverExtInstance, parkPos, returnPos)) { + if (!extCitizenInstanceManager.CalculateReturnPath(ref driverExtInstance, parkPos, returnPos)) { #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Could not calculate return path for citizen instance {driverExtInstance.instanceId}, vehicle {vehicleID}. Resetting instance."); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Could not calculate return path for citizen instance {driverExtInstance.instanceId}, vehicle {vehicleID}. Resetting instance."); #endif - extCitizenInstanceManager.Reset(ref driverExtInstance); - return false; - } - } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { - // no alternative parking spot found: abort + extCitizenInstanceManager.Reset(ref driverExtInstance); + return false; + } + } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { + // no alternative parking spot found: abort #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No alternative parking spot found for vehicle {vehicleID}, citizen instance {driverExtInstance.instanceId} with CurrentPathMode={driverExtInstance.pathMode}! GIVING UP."); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No alternative parking spot found for vehicle {vehicleID}, citizen instance {driverExtInstance.instanceId} with CurrentPathMode={driverExtInstance.pathMode}! GIVING UP."); #endif - extCitizenInstanceManager.Reset(ref driverExtInstance); - return false; - } else { - // calculate a direct path to target + extCitizenInstanceManager.Reset(ref driverExtInstance); + return false; + } else { + // calculate a direct path to target #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No alternative parking spot found for vehicle {vehicleID}, citizen instance {driverExtInstance.instanceId} with CurrentPathMode={driverExtInstance.pathMode}! Setting CurrentPathMode to 'CalculatingCarPath'."); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No alternative parking spot found for vehicle {vehicleID}, citizen instance {driverExtInstance.instanceId} with CurrentPathMode={driverExtInstance.pathMode}! Setting CurrentPathMode to 'CalculatingCarPath'."); #endif - driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; - } - } + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; + } + } - extPathType = driverExtInstance.GetPathType(); - driverExtInstance.atOutsideConnection = Constants.ManagerFactory.ExtCitizenInstanceManager.IsAtOutsideConnection(driverInstanceId, ref driverInstance, ref driverExtInstance, startPos); - } + extPathType = driverExtInstance.GetPathType(); + driverExtInstance.atOutsideConnection = Constants.ManagerFactory.ExtCitizenInstanceManager.IsAtOutsideConnection(driverInstanceId, ref driverInstance, ref driverExtInstance, startPos); + } #if BENCHMARK } #endif - NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle; - if (!movingToParkingPos) { - laneTypes |= NetInfo.LaneType.Pedestrian; - - if (Options.parkingAI && (driverInstance.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { - /* - * citizen may use public transport - */ - laneTypes |= NetInfo.LaneType.PublicTransport; - - uint citizenId = driverInstance.m_citizen; - if (citizenId != 0u && (Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) { - laneTypes |= NetInfo.LaneType.EvacuationTransport; - } - } - } - // NON-STOCK CODE END - - VehicleInfo.VehicleType vehicleTypes = vehicleInfo.m_vehicleType; - bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0; - bool randomParking = false; - bool combustionEngine = vehicleInfo.m_class.m_subService == ItemClass.SubService.ResidentialLow; - if (allowRandomParking && // NON-STOCK CODE - !movingToParkingPos && - targetBuildingId != 0 && - ( - Singleton.instance.m_buildings.m_buffer[(int)targetBuildingId].Info.m_class.m_service > ItemClass.Service.Office || - (driverInstance.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None - )) { - randomParking = true; - } - -#if DEBUG - if (fineDebug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Requesting path-finding for passenger car {vehicleID}, startPos={startPos}, endPos={endPos}, extPathType={extPathType}"); -#endif - - // NON-STOCK CODE START - if (!foundStartingPos) { - foundStartingPos = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, vehicleTypes, allowUnderground, false, 32f, out startPosA, out startPosB, out sqrDistA, out sqrDistB); - } - - bool foundEndPos = !calculateEndPos || driverInstance.Info.m_citizenAI.FindPathPosition(driverInstanceId, ref driverInstance, endPos, Options.parkingAI && (targetBuildingId == 0 || (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None) ? NetInfo.LaneType.Pedestrian : (laneTypes | NetInfo.LaneType.Pedestrian), vehicleTypes, undergroundTarget, out endPosA); - // NON-STOCK CODE END - - if (foundStartingPos && - foundEndPos) { // NON-STOCK CODE - - if (!startBothWays || sqrDistA < 10f) { - startPosB = default(PathUnit.Position); - } - PathUnit.Position endPosB = default(PathUnit.Position); - SimulationManager simMan = Singleton.instance; - uint path; - PathUnit.Position dummyPathPos = default(PathUnit.Position); - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = extPathType; - args.extVehicleType = ExtVehicleType.PassengerCar; - args.vehicleId = vehicleID; - args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - args.buildIndex = simMan.m_currentBuildIndex; - args.startPosA = startPosA; - args.startPosB = startPosB; - args.endPosA = endPosA; - args.endPosB = endPosB; - args.vehiclePosition = dummyPathPos; - args.laneTypes = laneTypes; - args.vehicleTypes = vehicleTypes; - args.maxLength = 20000f; - args.isHeavyVehicle = isHeavyVehicle; - args.hasCombustionEngine = hasCombustionEngine; - args.ignoreBlocked = ignoreBlocked; - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = randomParking; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - - if (CustomPathManager._instance.CustomCreatePath(out path, ref simMan.m_randomizer, args)) { -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Path-finding starts for passenger car {vehicleID}, path={path}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, startPosA.offset={startPosA.m_offset}, startPosB.segment={startPosB.m_segment}, startPosB.lane={startPosB.m_lane}, startPosB.offset={startPosB.m_offset}, laneType={laneTypes}, vehicleType={vehicleTypes}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, endPosA.offset={endPosA.m_offset}, endPosB.segment={endPosB.m_segment}, endPosB.lane={endPosB.m_lane}, endPosB.offset={endPosB.m_offset}"); -#endif - // NON-STOCK CODE END - - if (vehicleData.m_path != 0u) { - Singleton.instance.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - - if (Options.parkingAI) { - extCitizenInstanceManager.Reset(ref driverExtInstance); - } - return false; - } - - public bool IsSpaceReservationAllowed(ushort transitNodeId, PathUnit.Position sourcePos, PathUnit.Position targetPos) { - if (!Options.timedLightsEnabled) { - return true; - } - - if (TrafficLightSimulationManager.Instance.HasActiveTimedSimulation(transitNodeId)) { - RoadBaseAI.TrafficLightState vehLightState; - RoadBaseAI.TrafficLightState pedLightState; -#if DEBUG - Vehicle dummyVeh = default(Vehicle); -#endif - Constants.ManagerFactory.TrafficLightSimulationManager.GetTrafficLightState( -#if DEBUG - 0, ref dummyVeh, -#endif - transitNodeId, sourcePos.m_segment, sourcePos.m_lane, targetPos.m_segment, ref Singleton.instance.m_segments.m_buffer[sourcePos.m_segment], 0, out vehLightState, out pedLightState); - - if (vehLightState == RoadBaseAI.TrafficLightState.Red) { - return false; - } - } - return true; - } - - /// - /// Checks for traffic lights and priority signs when changing segments (for rail vehicles). - /// Sets the maximum allowed speed if segment change is not allowed (otherwise has to be set by the calling method). - /// - /// vehicle id - /// vehicle data - /// last frame squared velocity - /// previous path position - /// previous target node - /// previous lane - /// current path position - /// transit node - /// current lane - /// true, if the vehicle may change segments, false otherwise. - public bool MayChangeSegment(ushort frontVehicleId, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID) { - VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref DUMMY_POS, 0); - Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); - return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; - } - - /// - /// Checks for traffic lights and priority signs when changing segments (for road & rail vehicles). - /// Sets the maximum allowed speed if segment change is not allowed (otherwise has to be set by the calling method). - /// - /// vehicle id - /// vehicle data - /// last frame squared velocity - /// previous path position - /// previous target node - /// previous lane - /// current path position - /// transit node - /// current lane - /// next path position - /// next target node - /// true, if the vehicle may change segments, false otherwise. - public bool MayChangeSegment(ushort frontVehicleId, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId) { - VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref nextPosition, nextTargetNodeId); - Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); - return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; - } - - protected VehicleJunctionTransitState MayChangeSegment(ushort frontVehicleId, ref ExtVehicle extVehicle, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId) { - //public bool MayChangeSegment(ushort frontVehicleId, ref VehicleState vehicleState, ref Vehicle vehicleData, float sqrVelocity, bool isRecklessDriver, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId, out float maxSpeed) { -#if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || targetNodeId == GlobalConfig.Instance.Debug.NodeId); -#endif - - if (prevTargetNodeId != targetNodeId - || (vehicleData.m_blockCounter == 255 && !VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) // NON-STOCK CODE - ) { - // method should only be called if targetNodeId == prevTargetNode - return VehicleJunctionTransitState.Leave; - } - - if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Leave) { - // vehicle was already allowed to leave the junction - if ( - sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity && - (extVehicle.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None - ) { - // vehicle is not moving. reset allowance to leave junction -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState from LEAVE to BLOCKED (speed to low)"); -#endif - return VehicleJunctionTransitState.Blocked; - } else { - return VehicleJunctionTransitState.Leave; - } - } - - uint currentFrameIndex = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; - if ((extVehicle.junctionTransitState == VehicleJunctionTransitState.Stop || extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) && - extVehicle.lastTransitStateUpdate >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT >= currentFrameIndex >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT) { - // reuse recent result - return extVehicle.junctionTransitState; - } - - bool isRecklessDriver = extVehicle.recklessDriver; - - var netManager = Singleton.instance; - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - - bool hasActiveTimedSimulation = (Options.timedLightsEnabled && TrafficLightSimulationManager.Instance.HasActiveTimedSimulation(targetNodeId)); - bool hasTrafficLightFlag = (targetNode.m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; - if (hasActiveTimedSimulation && !hasTrafficLightFlag) { - TrafficLightManager.Instance.AddTrafficLight(targetNodeId, ref targetNode); - } - bool hasTrafficLight = hasTrafficLightFlag || hasActiveTimedSimulation; - bool checkTrafficLights = true; - bool isTargetStartNode = prevSegment.m_startNode == targetNodeId; - bool isLevelCrossing = (targetNode.m_flags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None; - if ((vehicleData.Info.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None) { - // check if to check space - -#if DEBUG - if (debug) - Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {frontVehicleId} is not a train."); -#endif - - // stock priority signs - if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == (Vehicle.Flags)0 && - ((NetLane.Flags)netManager.m_lanes.m_buffer[prevLaneID].m_flags & (NetLane.Flags.YieldStart | NetLane.Flags.YieldEnd)) != NetLane.Flags.None && - (targetNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.TrafficLights | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction) { - if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { - if ((vehicleData.m_flags2 & Vehicle.Flags2.Yielding) == (Vehicle.Flags2)0) { - if (sqrVelocity < 0.01f) { - vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; - } - return VehicleJunctionTransitState.Stop; - } else { - vehicleData.m_waitCounter = (byte)Mathf.Min((int)(vehicleData.m_waitCounter + 1), 4); - if (vehicleData.m_waitCounter < 4) { - return VehicleJunctionTransitState.Stop; - } - vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; - vehicleData.m_waitCounter = 0; - } - } else if (sqrVelocity > 0.01f) { - return VehicleJunctionTransitState.Stop; - } - } - - // entering blocked junctions - if (MustCheckSpace(prevPos.m_segment, isTargetStartNode, ref targetNode, isRecklessDriver)) { - // check if there is enough space - var len = extVehicle.totalLength + 4f; - if (!netManager.m_lanes.m_buffer[laneID].CheckSpace(len)) { - var sufficientSpace = false; - if (nextPosition.m_segment != 0 && netManager.m_lanes.m_buffer[laneID].m_length < 30f) { - NetNode.Flags nextTargetNodeFlags = netManager.m_nodes.m_buffer[nextTargetNodeId].m_flags; - if ((nextTargetNodeFlags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) != NetNode.Flags.Junction || - netManager.m_nodes.m_buffer[nextTargetNodeId].CountSegments() == 2) { - uint nextLaneId = PathManager.GetLaneID(nextPosition); - if (nextLaneId != 0u) { - sufficientSpace = netManager.m_lanes.m_buffer[nextLaneId].CheckSpace(len); - } - } - } - - if (!sufficientSpace) { -#if DEBUG - if (debug) - Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState to BLOCKED"); -#endif - - return VehicleJunctionTransitState.Blocked; - } - } - } - - bool isJoinedJunction = ((NetLane.Flags)netManager.m_lanes.m_buffer[prevLaneID].m_flags & NetLane.Flags.JoinedJunction) != NetLane.Flags.None; - checkTrafficLights = !isJoinedJunction || isLevelCrossing; - } else { -#if DEBUG - if (debug) - Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {frontVehicleId} is a train/metro/monorail."); -#endif - - if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { - // vanilla traffic light flags are not rendered on monorail tracks - checkTrafficLights = hasActiveTimedSimulation; - } else if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { - // vanilla traffic light flags are not rendered on train tracks, except for level crossings - checkTrafficLights = hasActiveTimedSimulation || isLevelCrossing; - } - } - - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - VehicleJunctionTransitState transitState = extVehicle.junctionTransitState; - if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) { -#if DEBUG - if (debug) - Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState from BLOCKED to APPROACH"); -#endif - transitState = VehicleJunctionTransitState.Approach; - } - - ITrafficPriorityManager prioMan = TrafficPriorityManager.Instance; - ICustomSegmentLightsManager segLightsMan = CustomSegmentLightsManager.Instance; - if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == 0 || isLevelCrossing) { - if (hasTrafficLight && checkTrafficLights) { -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Node {targetNodeId} has a traffic light."); - } -#endif - bool stopCar = false; - uint simGroup = (uint)targetNodeId >> 7; - - RoadBaseAI.TrafficLightState vehicleLightState; - RoadBaseAI.TrafficLightState pedestrianLightState; - bool vehicles; - bool pedestrians; - Constants.ManagerFactory.TrafficLightSimulationManager.GetTrafficLightState( -#if DEBUG - frontVehicleId, ref vehicleData, -#endif - targetNodeId, prevPos.m_segment, prevPos.m_lane, position.m_segment, ref prevSegment, currentFrameIndex - simGroup, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); // TODO current frame index or reference frame index? - - if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car && isRecklessDriver && !isLevelCrossing) { - vehicleLightState = RoadBaseAI.TrafficLightState.Green; - } - -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle {frontVehicleId} has TL state {vehicleLightState} at node {targetNodeId} (recklessDriver={isRecklessDriver})"); -#endif - - uint random = currentFrameIndex - simGroup & 255u; - if (!vehicles && random >= 196u) { - vehicles = true; - RoadBaseAI.SetTrafficLightState(targetNodeId, ref prevSegment, currentFrameIndex - simGroup, vehicleLightState, pedestrianLightState, vehicles, pedestrians); - } - - switch (vehicleLightState) { - case RoadBaseAI.TrafficLightState.RedToGreen: - if (random < 60u) { - stopCar = true; - } - break; - case RoadBaseAI.TrafficLightState.Red: - stopCar = true; - break; - case RoadBaseAI.TrafficLightState.GreenToRed: - if (random >= 30u) { - stopCar = true; - } - break; - } + NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle; + if (!movingToParkingPos) { + laneTypes |= NetInfo.LaneType.Pedestrian; + + if (Options.parkingAI && (driverInstance.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { + /* + * citizen may use public transport + */ + laneTypes |= NetInfo.LaneType.PublicTransport; + + uint citizenId = driverInstance.m_citizen; + if (citizenId != 0u && (Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) { + laneTypes |= NetInfo.LaneType.EvacuationTransport; + } + } + } + // NON-STOCK CODE END + + VehicleInfo.VehicleType vehicleTypes = vehicleInfo.m_vehicleType; + bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0; + bool randomParking = false; + bool combustionEngine = vehicleInfo.m_class.m_subService == ItemClass.SubService.ResidentialLow; + if (allowRandomParking && // NON-STOCK CODE + !movingToParkingPos && + targetBuildingId != 0 && + ( + Singleton.instance.m_buildings.m_buffer[(int)targetBuildingId].Info.m_class.m_service > ItemClass.Service.Office || + (driverInstance.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None + )) { + randomParking = true; + } + +#if DEBUG + if (fineDebug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Requesting path-finding for passenger car {vehicleID}, startPos={startPos}, endPos={endPos}, extPathType={extPathType}"); +#endif + + // NON-STOCK CODE START + if (!foundStartingPos) { + foundStartingPos = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, vehicleTypes, allowUnderground, false, 32f, out startPosA, out startPosB, out sqrDistA, out sqrDistB); + } + + bool foundEndPos = !calculateEndPos || driverInstance.Info.m_citizenAI.FindPathPosition(driverInstanceId, ref driverInstance, endPos, Options.parkingAI && (targetBuildingId == 0 || (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None) ? NetInfo.LaneType.Pedestrian : (laneTypes | NetInfo.LaneType.Pedestrian), vehicleTypes, undergroundTarget, out endPosA); + // NON-STOCK CODE END + + if (foundStartingPos && + foundEndPos) { // NON-STOCK CODE + + if (!startBothWays || sqrDistA < 10f) { + startPosB = default(PathUnit.Position); + } + PathUnit.Position endPosB = default(PathUnit.Position); + SimulationManager simMan = Singleton.instance; + uint path; + PathUnit.Position dummyPathPos = default(PathUnit.Position); + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = extPathType; + args.extVehicleType = ExtVehicleType.PassengerCar; + args.vehicleId = vehicleID; + args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + args.buildIndex = simMan.m_currentBuildIndex; + args.startPosA = startPosA; + args.startPosB = startPosB; + args.endPosA = endPosA; + args.endPosB = endPosB; + args.vehiclePosition = dummyPathPos; + args.laneTypes = laneTypes; + args.vehicleTypes = vehicleTypes; + args.maxLength = 20000f; + args.isHeavyVehicle = isHeavyVehicle; + args.hasCombustionEngine = hasCombustionEngine; + args.ignoreBlocked = ignoreBlocked; + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = randomParking; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + + if (CustomPathManager._instance.CustomCreatePath(out path, ref simMan.m_randomizer, args)) { +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Path-finding starts for passenger car {vehicleID}, path={path}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, startPosA.offset={startPosA.m_offset}, startPosB.segment={startPosB.m_segment}, startPosB.lane={startPosB.m_lane}, startPosB.offset={startPosB.m_offset}, laneType={laneTypes}, vehicleType={vehicleTypes}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, endPosA.offset={endPosA.m_offset}, endPosB.segment={endPosB.m_segment}, endPosB.lane={endPosB.m_lane}, endPosB.offset={endPosB.m_offset}"); +#endif + // NON-STOCK CODE END + + if (vehicleData.m_path != 0u) { + Singleton.instance.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + + if (Options.parkingAI) { + extCitizenInstanceManager.Reset(ref driverExtInstance); + } + return false; + } + + public bool IsSpaceReservationAllowed(ushort transitNodeId, PathUnit.Position sourcePos, PathUnit.Position targetPos) { + if (!Options.timedLightsEnabled) { + return true; + } + + if (TrafficLightSimulationManager.Instance.HasActiveTimedSimulation(transitNodeId)) { + RoadBaseAI.TrafficLightState vehLightState; + RoadBaseAI.TrafficLightState pedLightState; +#if DEBUG + Vehicle dummyVeh = default(Vehicle); +#endif + Constants.ManagerFactory.TrafficLightSimulationManager.GetTrafficLightState( +#if DEBUG + 0, ref dummyVeh, +#endif + transitNodeId, sourcePos.m_segment, sourcePos.m_lane, targetPos.m_segment, ref Singleton.instance.m_segments.m_buffer[sourcePos.m_segment], 0, out vehLightState, out pedLightState); + + if (vehLightState == RoadBaseAI.TrafficLightState.Red) { + return false; + } + } + return true; + } + + /// + /// Checks for traffic lights and priority signs when changing segments (for rail vehicles). + /// Sets the maximum allowed speed if segment change is not allowed (otherwise has to be set by the calling method). + /// + /// vehicle id + /// vehicle data + /// last frame squared velocity + /// previous path position + /// previous target node + /// previous lane + /// current path position + /// transit node + /// current lane + /// true, if the vehicle may change segments, false otherwise. + public bool MayChangeSegment(ushort frontVehicleId, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID) { + VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref DUMMY_POS, 0); + Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); + return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; + } + + /// + /// Checks for traffic lights and priority signs when changing segments (for road & rail vehicles). + /// Sets the maximum allowed speed if segment change is not allowed (otherwise has to be set by the calling method). + /// + /// vehicle id + /// vehicle data + /// last frame squared velocity + /// previous path position + /// previous target node + /// previous lane + /// current path position + /// transit node + /// current lane + /// next path position + /// next target node + /// true, if the vehicle may change segments, false otherwise. + public bool MayChangeSegment(ushort frontVehicleId, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId) { + VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref nextPosition, nextTargetNodeId); + Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); + return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; + } + + protected VehicleJunctionTransitState MayChangeSegment(ushort frontVehicleId, ref ExtVehicle extVehicle, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId) { + //public bool MayChangeSegment(ushort frontVehicleId, ref VehicleState vehicleState, ref Vehicle vehicleData, float sqrVelocity, bool isRecklessDriver, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId, out float maxSpeed) { +#if DEBUG + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || targetNodeId == GlobalConfig.Instance.Debug.NodeId); +#endif + + if (prevTargetNodeId != targetNodeId + || (vehicleData.m_blockCounter == 255 && !VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) // NON-STOCK CODE + ) { + // method should only be called if targetNodeId == prevTargetNode + return VehicleJunctionTransitState.Leave; + } + + if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Leave) { + // vehicle was already allowed to leave the junction + if ( + sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity && + (extVehicle.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None + ) { + // vehicle is not moving. reset allowance to leave junction +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState from LEAVE to BLOCKED (speed to low)"); +#endif + return VehicleJunctionTransitState.Blocked; + } else { + return VehicleJunctionTransitState.Leave; + } + } + + uint currentFrameIndex = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; + if ((extVehicle.junctionTransitState == VehicleJunctionTransitState.Stop || extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) && + extVehicle.lastTransitStateUpdate >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT >= currentFrameIndex >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT) { + // reuse recent result + return extVehicle.junctionTransitState; + } + + bool isRecklessDriver = extVehicle.recklessDriver; + + var netManager = Singleton.instance; + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + + bool hasActiveTimedSimulation = (Options.timedLightsEnabled && TrafficLightSimulationManager.Instance.HasActiveTimedSimulation(targetNodeId)); + bool hasTrafficLightFlag = (targetNode.m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; + if (hasActiveTimedSimulation && !hasTrafficLightFlag) { + TrafficLightManager.Instance.AddTrafficLight(targetNodeId, ref targetNode); + } + bool hasTrafficLight = hasTrafficLightFlag || hasActiveTimedSimulation; + bool checkTrafficLights = true; + bool isTargetStartNode = prevSegment.m_startNode == targetNodeId; + bool isLevelCrossing = (targetNode.m_flags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None; + if ((vehicleData.Info.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None) { + // check if to check space + +#if DEBUG + if (debug) + Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {frontVehicleId} is not a train."); +#endif + + // stock priority signs + if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == (Vehicle.Flags)0 && + ((NetLane.Flags)netManager.m_lanes.m_buffer[prevLaneID].m_flags & (NetLane.Flags.YieldStart | NetLane.Flags.YieldEnd)) != NetLane.Flags.None && + (targetNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.TrafficLights | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction) { + if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { + if ((vehicleData.m_flags2 & Vehicle.Flags2.Yielding) == (Vehicle.Flags2)0) { + if (sqrVelocity < 0.01f) { + vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; + } + return VehicleJunctionTransitState.Stop; + } else { + vehicleData.m_waitCounter = (byte)Mathf.Min((int)(vehicleData.m_waitCounter + 1), 4); + if (vehicleData.m_waitCounter < 4) { + return VehicleJunctionTransitState.Stop; + } + vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; + vehicleData.m_waitCounter = 0; + } + } else if (sqrVelocity > 0.01f) { + return VehicleJunctionTransitState.Stop; + } + } + + // entering blocked junctions + if (MustCheckSpace(prevPos.m_segment, isTargetStartNode, ref targetNode, isRecklessDriver)) { + // check if there is enough space + var len = extVehicle.totalLength + 4f; + if (!netManager.m_lanes.m_buffer[laneID].CheckSpace(len)) { + var sufficientSpace = false; + if (nextPosition.m_segment != 0 && netManager.m_lanes.m_buffer[laneID].m_length < 30f) { + NetNode.Flags nextTargetNodeFlags = netManager.m_nodes.m_buffer[nextTargetNodeId].m_flags; + if ((nextTargetNodeFlags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) != NetNode.Flags.Junction || + netManager.m_nodes.m_buffer[nextTargetNodeId].CountSegments() == 2) { + uint nextLaneId = PathManager.GetLaneID(nextPosition); + if (nextLaneId != 0u) { + sufficientSpace = netManager.m_lanes.m_buffer[nextLaneId].CheckSpace(len); + } + } + } + + if (!sufficientSpace) { +#if DEBUG + if (debug) + Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState to BLOCKED"); +#endif + + return VehicleJunctionTransitState.Blocked; + } + } + } + + bool isJoinedJunction = ((NetLane.Flags)netManager.m_lanes.m_buffer[prevLaneID].m_flags & NetLane.Flags.JoinedJunction) != NetLane.Flags.None; + checkTrafficLights = !isJoinedJunction || isLevelCrossing; + } else { +#if DEBUG + if (debug) + Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {frontVehicleId} is a train/metro/monorail."); +#endif + + if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { + // vanilla traffic light flags are not rendered on monorail tracks + checkTrafficLights = hasActiveTimedSimulation; + } else if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { + // vanilla traffic light flags are not rendered on train tracks, except for level crossings + checkTrafficLights = hasActiveTimedSimulation || isLevelCrossing; + } + } + + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + VehicleJunctionTransitState transitState = extVehicle.junctionTransitState; + if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) { +#if DEBUG + if (debug) + Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState from BLOCKED to APPROACH"); +#endif + transitState = VehicleJunctionTransitState.Approach; + } + + ITrafficPriorityManager prioMan = TrafficPriorityManager.Instance; + ICustomSegmentLightsManager segLightsMan = CustomSegmentLightsManager.Instance; + if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == 0 || isLevelCrossing) { + if (hasTrafficLight && checkTrafficLights) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Node {targetNodeId} has a traffic light."); + } +#endif + bool stopCar = false; + uint simGroup = (uint)targetNodeId >> 7; + + RoadBaseAI.TrafficLightState vehicleLightState; + RoadBaseAI.TrafficLightState pedestrianLightState; + bool vehicles; + bool pedestrians; + Constants.ManagerFactory.TrafficLightSimulationManager.GetTrafficLightState( +#if DEBUG + frontVehicleId, ref vehicleData, +#endif + targetNodeId, prevPos.m_segment, prevPos.m_lane, position.m_segment, ref prevSegment, currentFrameIndex - simGroup, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); // TODO current frame index or reference frame index? + + if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car && isRecklessDriver && !isLevelCrossing) { + vehicleLightState = RoadBaseAI.TrafficLightState.Green; + } + +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle {frontVehicleId} has TL state {vehicleLightState} at node {targetNodeId} (recklessDriver={isRecklessDriver})"); +#endif + + uint random = currentFrameIndex - simGroup & 255u; + if (!vehicles && random >= 196u) { + vehicles = true; + RoadBaseAI.SetTrafficLightState(targetNodeId, ref prevSegment, currentFrameIndex - simGroup, vehicleLightState, pedestrianLightState, vehicles, pedestrians); + } + + switch (vehicleLightState) { + case RoadBaseAI.TrafficLightState.RedToGreen: + if (random < 60u) { + stopCar = true; + } + break; + case RoadBaseAI.TrafficLightState.Red: + stopCar = true; + break; + case RoadBaseAI.TrafficLightState.GreenToRed: + if (random >= 30u) { + stopCar = true; + } + break; + } #if TURNONRED // Check if turning in the preferred direction, and if turning while it's red is allowed @@ -889,1179 +879,1179 @@ protected VehicleJunctionTransitState MayChangeSegment(ushort frontVehicleId, re } #endif - // Turn-on-red: Check if turning in the preferred direction, and if turning while it's red is allowed - if ( - Options.turnOnRedEnabled && - stopCar && - (extVehicle.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None && - sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity && - !isRecklessDriver - ) { - IJunctionRestrictionsManager junctionRestrictionsManager = Constants.ManagerFactory.JunctionRestrictionsManager; - ITurnOnRedManager turnOnRedMan = Constants.ManagerFactory.TurnOnRedManager; - bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; - int torIndex = turnOnRedMan.GetIndex(prevPos.m_segment, isTargetStartNode); - if ( - (turnOnRedMan.TurnOnRedSegments[torIndex].leftSegmentId == position.m_segment && - junctionRestrictionsManager.IsTurnOnRedAllowed(lhd, prevPos.m_segment, isTargetStartNode)) || - (turnOnRedMan.TurnOnRedSegments[torIndex].rightSegmentId == position.m_segment && - junctionRestrictionsManager.IsTurnOnRedAllowed(!lhd, prevPos.m_segment, isTargetStartNode)) - ) { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle may turn on red to target segment {position.m_segment}, lane {position.m_lane}"); -#endif - stopCar = false; - } - } - - // check priority rules at unprotected traffic lights - if (!stopCar && Options.prioritySignsEnabled && Options.trafficLightPriorityRules && segLightsMan.IsSegmentLight(prevPos.m_segment, isTargetStartNode)) { - bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], targetNodeId, isTargetStartNode, ref position, ref targetNode); - - if (!hasPriority) { - // green light but other cars are incoming and they have priority: stop -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Green traffic light (or turn on red allowed) but detected traffic with higher priority: stop."); -#endif - stopCar = true; - } - } - - if (stopCar) { + // Turn-on-red: Check if turning in the preferred direction, and if turning while it's red is allowed + if ( + Options.turnOnRedEnabled && + stopCar && + (extVehicle.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None && + sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity && + !isRecklessDriver + ) { + IJunctionRestrictionsManager junctionRestrictionsManager = Constants.ManagerFactory.JunctionRestrictionsManager; + ITurnOnRedManager turnOnRedMan = Constants.ManagerFactory.TurnOnRedManager; + bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; + int torIndex = turnOnRedMan.GetIndex(prevPos.m_segment, isTargetStartNode); + if ( + (turnOnRedMan.TurnOnRedSegments[torIndex].leftSegmentId == position.m_segment && + junctionRestrictionsManager.IsTurnOnRedAllowed(lhd, prevPos.m_segment, isTargetStartNode)) || + (turnOnRedMan.TurnOnRedSegments[torIndex].rightSegmentId == position.m_segment && + junctionRestrictionsManager.IsTurnOnRedAllowed(!lhd, prevPos.m_segment, isTargetStartNode)) + ) { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP"); + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle may turn on red to target segment {position.m_segment}, lane {position.m_lane}"); #endif + stopCar = false; + } + } - if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { - vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; - vehicleData.m_waitCounter = 0; - } + // check priority rules at unprotected traffic lights + if (!stopCar && Options.prioritySignsEnabled && Options.trafficLightPriorityRules && segLightsMan.IsSegmentLight(prevPos.m_segment, isTargetStartNode)) { + bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], targetNodeId, isTargetStartNode, ref position, ref targetNode); - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } else { + if (!hasPriority) { + // green light but other cars are incoming and they have priority: stop #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE ({vehicleLightState})"); + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Green traffic light (or turn on red allowed) but detected traffic with higher priority: stop."); #endif + stopCar = true; + } + } - if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { - vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; - vehicleData.m_waitCounter = 0; - } - - return VehicleJunctionTransitState.Leave; - } - } else if (Options.prioritySignsEnabled && vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { + if (stopCar) { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light."); + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP"); #endif - var sign = prioMan.GetPrioritySign(prevPos.m_segment, isTargetStartNode); - if (sign != PriorityType.None) { -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light and is a priority segment."); - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): JunctionTransitState={transitState.ToString()}"); - } -#endif + if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { + vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; + vehicleData.m_waitCounter = 0; + } - if (transitState == VehicleJunctionTransitState.None) { + vehicleData.m_blockCounter = 0; + return VehicleJunctionTransitState.Stop; + } else { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to APPROACH (prio)"); + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE ({vehicleLightState})"); #endif - transitState = VehicleJunctionTransitState.Approach; - } - - if (sign == PriorityType.Stop) { - if (transitState == VehicleJunctionTransitState.Approach) { - extVehicle.waitTime = 0; - } - if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { - ++extVehicle.waitTime; + if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { + vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; + vehicleData.m_waitCounter = 0; + } - if (extVehicle.waitTime < 2) { - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } - } else { + return VehicleJunctionTransitState.Leave; + } + } else if (Options.prioritySignsEnabled && vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has come to a full stop."); -#endif - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } - } - - if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity) { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): {sign} sign. waittime={extVehicle.waitTime}"); -#endif - if (extVehicle.waitTime < GlobalConfig.Instance.PriorityRules.MaxPriorityWaitTime) { - extVehicle.waitTime++; -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP (wait)"); -#endif - bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], targetNodeId, isTargetStartNode, ref position, ref targetNode); -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): hasPriority: {hasPriority}"); -#endif - - if (!hasPriority) { - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } else { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (no conflicting cars)"); -#endif - return VehicleJunctionTransitState.Leave; - } - } else { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (max wait timeout)"); -#endif - return VehicleJunctionTransitState.Leave; - } - } else { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has not yet reached yield speed (sqrVelocity={sqrVelocity})"); + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light."); #endif - // vehicle has not yet reached yield speed - return VehicleJunctionTransitState.Stop; - } - } else { - return VehicleJunctionTransitState.Leave; - } - } else { - return VehicleJunctionTransitState.Leave; - } - } else { - return VehicleJunctionTransitState.Leave; - } - } - - /// - /// Checks if a vehicle must check if the subsequent segment is empty while going from segment - /// through node . - /// - /// source segment id - /// is transit node start node of source segment? - /// transit node - /// reckless driver? - /// - protected bool MustCheckSpace(ushort segmentId, bool startNode, ref NetNode node, bool isRecklessDriver) { - bool checkSpace; - if (isRecklessDriver) { - checkSpace = (node.m_flags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None; - } else { - if (Options.junctionRestrictionsEnabled) { - checkSpace = !JunctionRestrictionsManager.Instance.IsEnteringBlockedJunctionAllowed(segmentId, startNode); - } else { - checkSpace = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction && node.CountSegments() != 2; - } - } - - return checkSpace; - } - - public bool MayDespawn(ref Vehicle vehicleData) { - return !Options.disableDespawning || ((vehicleData.m_flags2 & (Vehicle.Flags2.Blown | Vehicle.Flags2.Floating)) != 0) || (vehicleData.m_flags & Vehicle.Flags.Parking) != 0; - } - - public float CalcMaxSpeed(ushort vehicleId, ref ExtVehicle extVehicle, VehicleInfo vehicleInfo, PathUnit.Position position, ref NetSegment segment, Vector3 pos, float maxSpeed, bool emergency) { - if (Singleton.instance.m_treatWetAsSnow) { - DistrictManager districtManager = Singleton.instance; - byte district = districtManager.GetDistrict(pos); - DistrictPolicies.CityPlanning cityPlanningPolicies = districtManager.m_districts.m_buffer[(int)district].m_cityPlanningPolicies; - if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.StuddedTires) != DistrictPolicies.CityPlanning.None) { - if (Options.strongerRoadConditionEffects) { - if (maxSpeed > ICY_ROADS_STUDDED_MIN_SPEED) - maxSpeed = ICY_ROADS_STUDDED_MIN_SPEED + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - ICY_ROADS_STUDDED_MIN_SPEED); - } else { - maxSpeed *= 1f - (float)segment.m_wetness * 0.0005882353f; // vanilla: -15% .. ±0% - } - districtManager.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= DistrictPolicies.CityPlanning.StuddedTires; - } else { - if (Options.strongerRoadConditionEffects) { - if (maxSpeed > ICY_ROADS_MIN_SPEED) - maxSpeed = ICY_ROADS_MIN_SPEED + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - ICY_ROADS_MIN_SPEED); - } else { - maxSpeed *= 1f - (float)segment.m_wetness * 0.00117647066f; // vanilla: -30% .. ±0% - } - } - } else { - if (Options.strongerRoadConditionEffects) { - float minSpeed = Math.Min(maxSpeed * WET_ROADS_FACTOR, WET_ROADS_MAX_SPEED); // custom: -25% .. 0 - if (maxSpeed > minSpeed) - maxSpeed = minSpeed + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - minSpeed); - } else { - maxSpeed *= 1f - (float)segment.m_wetness * 0.0005882353f; // vanilla: -15% .. ±0% - } - } - - if (Options.strongerRoadConditionEffects) { - float minSpeed = Math.Min(maxSpeed * BROKEN_ROADS_FACTOR, BROKEN_ROADS_MAX_SPEED); - if (maxSpeed > minSpeed) { - maxSpeed = minSpeed + (float)segment.m_condition * 0.0039215686f * (maxSpeed - minSpeed); - } - } else { - maxSpeed *= 1f + (float)segment.m_condition * 0.0005882353f; // vanilla: ±0% .. +15 % - } - - maxSpeed = ApplyRealisticSpeeds(maxSpeed, vehicleId, ref extVehicle, vehicleInfo); - maxSpeed = Math.Max(MIN_SPEED, maxSpeed); // at least 10 km/h - - return maxSpeed; - } - - public float ApplyRealisticSpeeds(float speed, ushort vehicleId, ref ExtVehicle extVehicle, VehicleInfo vehicleInfo) { - if (Options.individualDrivingStyle) { - float vehicleRand = 0.01f * (float)Constants.ManagerFactory.ExtVehicleManager.GetTimedVehicleRand(vehicleId); - if (vehicleInfo.m_isLargeVehicle) { - speed *= 0.75f + vehicleRand * 0.25f; // a little variance, 0.75 .. 1 - } else if (extVehicle.recklessDriver) { - speed *= 1.3f + vehicleRand * 1.7f; // woohooo, 1.3 .. 3 - } else { - speed *= 0.8f + vehicleRand * 0.5f; // a little variance, 0.8 .. 1.3 - } - } else if (extVehicle.recklessDriver) { - speed *= 1.5f; - } - return speed; - } - - public bool IsRecklessDriver(ushort vehicleId, ref Vehicle vehicleData) { - if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { - return true; - } - if (Options.evacBussesMayIgnoreRules && vehicleData.Info.GetService() == ItemClass.Service.Disaster) { - return true; - } - if (Options.recklessDrivers == 3) { - return false; - } - if ((vehicleData.Info.m_vehicleType & RECKLESS_VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { - return false; - } - - return (uint)vehicleId % Options.getRecklessDriverModulo() == 0; - } - - public int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position next1PathPos, NetInfo next1SegInfo, PathUnit.Position next2PathPos, NetInfo next2SegInfo, PathUnit.Position next3PathPos, NetInfo next3SegInfo, PathUnit.Position next4PathPos) { - try { - GlobalConfig conf = GlobalConfig.Instance; + var sign = prioMan.GetPrioritySign(prevPos.m_segment, isTargetStartNode); + if (sign != PriorityType.None) { #if DEBUG - bool debug = false; - if (conf.Debug.Switches[17]) { - ushort nodeId = Services.NetService.GetSegmentNodeId(currentPathPos.m_segment, currentPathPos.m_offset < 128); - debug = (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId) && (conf.Debug.NodeId == 0 || conf.Debug.NodeId == nodeId); - } - - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): currentLaneId={currentLaneId}, currentPathPos=[seg={currentPathPos.m_segment}, lane={currentPathPos.m_lane}, off={currentPathPos.m_offset}] next1PathPos=[seg={next1PathPos.m_segment}, lane={next1PathPos.m_lane}, off={next1PathPos.m_offset}] next2PathPos=[seg={next2PathPos.m_segment}, lane={next2PathPos.m_lane}, off={next2PathPos.m_offset}] next3PathPos=[seg={next3PathPos.m_segment}, lane={next3PathPos.m_lane}, off={next3PathPos.m_offset}] next4PathPos=[seg={next4PathPos.m_segment}, lane={next4PathPos.m_lane}, off={next4PathPos.m_offset}]"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light and is a priority segment."); + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): JunctionTransitState={transitState.ToString()}"); + } #endif - if (!vehicleState.dlsReady) { - Constants.ManagerFactory.ExtVehicleManager.UpdateDynamicLaneSelectionParameters(ref vehicleState); - } - if (vehicleState.lastAltLaneSelSegmentId == currentPathPos.m_segment) { + if (transitState == VehicleJunctionTransitState.None) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping alternative lane selection: Already calculated."); - } + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to APPROACH (prio)"); #endif - return next1PathPos.m_lane; - } - vehicleState.lastAltLaneSelSegmentId = currentPathPos.m_segment; + transitState = VehicleJunctionTransitState.Approach; + } - bool recklessDriver = vehicleState.recklessDriver; - float maxReservedSpace = vehicleState.maxReservedSpace; + if (sign == PriorityType.Stop) { + if (transitState == VehicleJunctionTransitState.Approach) { + extVehicle.waitTime = 0; + } - // cur -> next1 - float vehicleLength = 1f + vehicleState.totalLength; - bool startNode = currentPathPos.m_offset < 128; - uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); + if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { + ++extVehicle.waitTime; -#if DEBUG - if (currentFwdRoutingIndex < 0 || currentFwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { - Log.Error($"Invalid array index: currentFwdRoutingIndex={currentFwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentLaneId={currentLaneId}, startNode={startNode})"); - } + if (extVehicle.waitTime < 2) { + vehicleData.m_blockCounter = 0; + return VehicleJunctionTransitState.Stop; + } + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has come to a full stop."); +#endif + vehicleData.m_blockCounter = 0; + return VehicleJunctionTransitState.Stop; + } + } + + if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity) { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): {sign} sign. waittime={extVehicle.waitTime}"); +#endif + if (extVehicle.waitTime < GlobalConfig.Instance.PriorityRules.MaxPriorityWaitTime) { + extVehicle.waitTime++; +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP (wait)"); +#endif + bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], targetNodeId, isTargetStartNode, ref position, ref targetNode); +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): hasPriority: {hasPriority}"); +#endif + + if (!hasPriority) { + vehicleData.m_blockCounter = 0; + return VehicleJunctionTransitState.Stop; + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (no conflicting cars)"); +#endif + return VehicleJunctionTransitState.Leave; + } + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (max wait timeout)"); +#endif + return VehicleJunctionTransitState.Leave; + } + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has not yet reached yield speed (sqrVelocity={sqrVelocity})"); +#endif + + // vehicle has not yet reached yield speed + return VehicleJunctionTransitState.Stop; + } + } else { + return VehicleJunctionTransitState.Leave; + } + } else { + return VehicleJunctionTransitState.Leave; + } + } else { + return VehicleJunctionTransitState.Leave; + } + } + + /// + /// Checks if a vehicle must check if the subsequent segment is empty while going from segment + /// through node . + /// + /// source segment id + /// is transit node start node of source segment? + /// transit node + /// reckless driver? + /// + protected bool MustCheckSpace(ushort segmentId, bool startNode, ref NetNode node, bool isRecklessDriver) { + bool checkSpace; + if (isRecklessDriver) { + checkSpace = (node.m_flags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None; + } else { + if (Options.junctionRestrictionsEnabled) { + checkSpace = !JunctionRestrictionsManager.Instance.IsEnteringBlockedJunctionAllowed(segmentId, startNode); + } else { + checkSpace = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction && node.CountSegments() != 2; + } + } + + return checkSpace; + } + + public bool MayDespawn(ref Vehicle vehicleData) { + return !Options.disableDespawning || ((vehicleData.m_flags2 & (Vehicle.Flags2.Blown | Vehicle.Flags2.Floating)) != 0) || (vehicleData.m_flags & Vehicle.Flags.Parking) != 0; + } + + public float CalcMaxSpeed(ushort vehicleId, ref ExtVehicle extVehicle, VehicleInfo vehicleInfo, PathUnit.Position position, ref NetSegment segment, Vector3 pos, float maxSpeed, bool emergency) { + if (Singleton.instance.m_treatWetAsSnow) { + DistrictManager districtManager = Singleton.instance; + byte district = districtManager.GetDistrict(pos); + DistrictPolicies.CityPlanning cityPlanningPolicies = districtManager.m_districts.m_buffer[(int)district].m_cityPlanningPolicies; + if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.StuddedTires) != DistrictPolicies.CityPlanning.None) { + if (Options.strongerRoadConditionEffects) { + if (maxSpeed > ICY_ROADS_STUDDED_MIN_SPEED) + maxSpeed = ICY_ROADS_STUDDED_MIN_SPEED + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - ICY_ROADS_STUDDED_MIN_SPEED); + } else { + maxSpeed *= 1f - (float)segment.m_wetness * 0.0005882353f; // vanilla: -15% .. ±0% + } + districtManager.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= DistrictPolicies.CityPlanning.StuddedTires; + } else { + if (Options.strongerRoadConditionEffects) { + if (maxSpeed > ICY_ROADS_MIN_SPEED) + maxSpeed = ICY_ROADS_MIN_SPEED + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - ICY_ROADS_MIN_SPEED); + } else { + maxSpeed *= 1f - (float)segment.m_wetness * 0.00117647066f; // vanilla: -30% .. ±0% + } + } + } else { + if (Options.strongerRoadConditionEffects) { + float minSpeed = Math.Min(maxSpeed * WET_ROADS_FACTOR, WET_ROADS_MAX_SPEED); // custom: -25% .. 0 + if (maxSpeed > minSpeed) + maxSpeed = minSpeed + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - minSpeed); + } else { + maxSpeed *= 1f - (float)segment.m_wetness * 0.0005882353f; // vanilla: -15% .. ±0% + } + } + + if (Options.strongerRoadConditionEffects) { + float minSpeed = Math.Min(maxSpeed * BROKEN_ROADS_FACTOR, BROKEN_ROADS_MAX_SPEED); + if (maxSpeed > minSpeed) { + maxSpeed = minSpeed + (float)segment.m_condition * 0.0039215686f * (maxSpeed - minSpeed); + } + } else { + maxSpeed *= 1f + (float)segment.m_condition * 0.0005882353f; // vanilla: ±0% .. +15 % + } + + maxSpeed = ApplyRealisticSpeeds(maxSpeed, vehicleId, ref extVehicle, vehicleInfo); + maxSpeed = Math.Max(MIN_SPEED, maxSpeed); // at least 10 km/h + + return maxSpeed; + } + + public float ApplyRealisticSpeeds(float speed, ushort vehicleId, ref ExtVehicle extVehicle, VehicleInfo vehicleInfo) { + if (Options.individualDrivingStyle) { + float vehicleRand = 0.01f * (float)Constants.ManagerFactory.ExtVehicleManager.GetTimedVehicleRand(vehicleId); + if (vehicleInfo.m_isLargeVehicle) { + speed *= 0.75f + vehicleRand * 0.25f; // a little variance, 0.75 .. 1 + } else if (extVehicle.recklessDriver) { + speed *= 1.3f + vehicleRand * 1.7f; // woohooo, 1.3 .. 3 + } else { + speed *= 0.8f + vehicleRand * 0.5f; // a little variance, 0.8 .. 1.3 + } + } else if (extVehicle.recklessDriver) { + speed *= 1.5f; + } + return speed; + } + + public bool IsRecklessDriver(ushort vehicleId, ref Vehicle vehicleData) { + if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { + return true; + } + if (Options.evacBussesMayIgnoreRules && vehicleData.Info.GetService() == ItemClass.Service.Disaster) { + return true; + } + if (Options.recklessDrivers == 3) { + return false; + } + if ((vehicleData.Info.m_vehicleType & RECKLESS_VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { + return false; + } + + return (uint)vehicleId % Options.getRecklessDriverModulo() == 0; + } + + public int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position next1PathPos, NetInfo next1SegInfo, PathUnit.Position next2PathPos, NetInfo next2SegInfo, PathUnit.Position next3PathPos, NetInfo next3SegInfo, PathUnit.Position next4PathPos) { + try { + GlobalConfig conf = GlobalConfig.Instance; +#if DEBUG + bool debug = false; + if (conf.Debug.Switches[17]) { + ushort nodeId = Services.NetService.GetSegmentNodeId(currentPathPos.m_segment, currentPathPos.m_offset < 128); + debug = (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId) && (conf.Debug.NodeId == 0 || conf.Debug.NodeId == nodeId); + } + + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): currentLaneId={currentLaneId}, currentPathPos=[seg={currentPathPos.m_segment}, lane={currentPathPos.m_lane}, off={currentPathPos.m_offset}] next1PathPos=[seg={next1PathPos.m_segment}, lane={next1PathPos.m_lane}, off={next1PathPos.m_offset}] next2PathPos=[seg={next2PathPos.m_segment}, lane={next2PathPos.m_lane}, off={next2PathPos.m_offset}] next3PathPos=[seg={next3PathPos.m_segment}, lane={next3PathPos.m_lane}, off={next3PathPos.m_offset}] next4PathPos=[seg={next4PathPos.m_segment}, lane={next4PathPos.m_lane}, off={next4PathPos.m_offset}]"); + } +#endif + if (!vehicleState.dlsReady) { + Constants.ManagerFactory.ExtVehicleManager.UpdateDynamicLaneSelectionParameters(ref vehicleState); + } + + if (vehicleState.lastAltLaneSelSegmentId == currentPathPos.m_segment) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping alternative lane selection: Already calculated."); + } +#endif + return next1PathPos.m_lane; + } + vehicleState.lastAltLaneSelSegmentId = currentPathPos.m_segment; + + bool recklessDriver = vehicleState.recklessDriver; + float maxReservedSpace = vehicleState.maxReservedSpace; + + // cur -> next1 + float vehicleLength = 1f + vehicleState.totalLength; + bool startNode = currentPathPos.m_offset < 128; + uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); + +#if DEBUG + if (currentFwdRoutingIndex < 0 || currentFwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { + Log.Error($"Invalid array index: currentFwdRoutingIndex={currentFwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentLaneId={currentLaneId}, startNode={startNode})"); + } +#endif + + if (!RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].routed) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward routing for next path position available."); + } #endif + return next1PathPos.m_lane; + } - if (!RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].routed) { -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward routing for next path position available."); - } -#endif - return next1PathPos.m_lane; - } + LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].transitions; - LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].transitions; - - if (currentFwdTransitions == null) { + if (currentFwdTransitions == null) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); + } #endif - return next1PathPos.m_lane; - } + return next1PathPos.m_lane; + } - VehicleInfo vehicleInfo = vehicleData.Info; - float vehicleMaxSpeed = vehicleInfo.m_maxSpeed / 8f; - float vehicleCurSpeed = vehicleData.GetLastFrameVelocity().magnitude / 8f; + VehicleInfo vehicleInfo = vehicleData.Info; + float vehicleMaxSpeed = vehicleInfo.m_maxSpeed / 8f; + float vehicleCurSpeed = vehicleData.GetLastFrameVelocity().magnitude / 8f; - float bestStayMeanSpeed = 0f; - float bestStaySpeedDiff = float.PositiveInfinity; // best speed difference on next continuous lane - int bestStayTotalLaneDist = int.MaxValue; - byte bestStayNext1LaneIndex = next1PathPos.m_lane; + float bestStayMeanSpeed = 0f; + float bestStaySpeedDiff = float.PositiveInfinity; // best speed difference on next continuous lane + int bestStayTotalLaneDist = int.MaxValue; + byte bestStayNext1LaneIndex = next1PathPos.m_lane; - float bestOptMeanSpeed = 0f; - float bestOptSpeedDiff = float.PositiveInfinity; // best speed difference on all next lanes - int bestOptTotalLaneDist = int.MaxValue; - byte bestOptNext1LaneIndex = next1PathPos.m_lane; + float bestOptMeanSpeed = 0f; + float bestOptSpeedDiff = float.PositiveInfinity; // best speed difference on all next lanes + int bestOptTotalLaneDist = int.MaxValue; + byte bestOptNext1LaneIndex = next1PathPos.m_lane; - bool foundSafeLaneChange = false; - //bool foundClearBackLane = false; - //bool foundClearFwdLane = false; + bool foundSafeLaneChange = false; + //bool foundClearBackLane = false; + //bool foundClearFwdLane = false; - //ushort reachableNext1LanesMask = 0; - uint reachableNext2LanesMask = 0; - uint reachableNext3LanesMask = 0; + //ushort reachableNext1LanesMask = 0; + uint reachableNext2LanesMask = 0; + uint reachableNext3LanesMask = 0; - //int numReachableNext1Lanes = 0; - int numReachableNext2Lanes = 0; - int numReachableNext3Lanes = 0; + //int numReachableNext1Lanes = 0; + int numReachableNext2Lanes = 0; + int numReachableNext3Lanes = 0; #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Starting lane-finding algorithm now. vehicleMaxSpeed={vehicleMaxSpeed}, vehicleCurSpeed={vehicleCurSpeed} vehicleLength={vehicleLength}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Starting lane-finding algorithm now. vehicleMaxSpeed={vehicleMaxSpeed}, vehicleCurSpeed={vehicleCurSpeed} vehicleLength={vehicleLength}"); + } #endif - uint mask; - for (int i = 0; i < currentFwdTransitions.Length; ++i) { - if (currentFwdTransitions[i].segmentId != next1PathPos.m_segment) { - continue; - } + uint mask; + for (int i = 0; i < currentFwdTransitions.Length; ++i) { + if (currentFwdTransitions[i].segmentId != next1PathPos.m_segment) { + continue; + } - if (!(currentFwdTransitions[i].type == LaneEndTransitionType.Default || - currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } + if (!(currentFwdTransitions[i].type == LaneEndTransitionType.Default || + currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || + (recklessDriver && currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed)) + ) { + continue; + } - if (currentFwdTransitions[i].distance > 1) { + if (currentFwdTransitions[i].distance > 1) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (distance too large)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (distance too large)"); + } #endif - continue; - } + continue; + } - if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next1PathPos.m_segment, currentFwdTransitions[i].laneIndex, next1SegInfo)) { + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next1PathPos.m_segment, currentFwdTransitions[i].laneIndex, next1SegInfo)) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); + } #endif - continue; - } + continue; + } - int minTotalLaneDist = int.MaxValue; + int minTotalLaneDist = int.MaxValue; - if (next2PathPos.m_segment != 0) { - // next1 -> next2 - uint next1FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, !currentFwdTransitions[i].startNode); + if (next2PathPos.m_segment != 0) { + // next1 -> next2 + uint next1FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, !currentFwdTransitions[i].startNode); #if DEBUG - if (next1FwdRoutingIndex < 0 || next1FwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { - Log.Error($"Invalid array index: next1FwdRoutingIndex={next1FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentFwdTransitions[i].laneId={currentFwdTransitions[i].laneId}, !currentFwdTransitions[i].startNode={!currentFwdTransitions[i].startNode})"); - } + if (next1FwdRoutingIndex < 0 || next1FwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { + Log.Error($"Invalid array index: next1FwdRoutingIndex={next1FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentFwdTransitions[i].laneId={currentFwdTransitions[i].laneId}, !currentFwdTransitions[i].startNode={!currentFwdTransitions[i].startNode})"); + } #endif #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next1 lane id={currentFwdTransitions[i].laneId}, seg.={currentFwdTransitions[i].segmentId}, index={currentFwdTransitions[i].laneIndex}, startNode={!currentFwdTransitions[i].startNode}: {RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex]}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next1 lane id={currentFwdTransitions[i].laneId}, seg.={currentFwdTransitions[i].segmentId}, index={currentFwdTransitions[i].laneIndex}, startNode={!currentFwdTransitions[i].startNode}: {RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex]}"); + } #endif - if (!RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex].routed) { - continue; - } - LaneTransitionData[] next1FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex].transitions; + if (!RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex].routed) { + continue; + } + LaneTransitionData[] next1FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex].transitions; - if (next1FwdTransitions == null) { - continue; - } + if (next1FwdTransitions == null) { + continue; + } - bool foundNext1Next2 = false; - for (int j = 0; j < next1FwdTransitions.Length; ++j) { - if (next1FwdTransitions[j].segmentId != next2PathPos.m_segment) { - continue; - } + bool foundNext1Next2 = false; + for (int j = 0; j < next1FwdTransitions.Length; ++j) { + if (next1FwdTransitions[j].segmentId != next2PathPos.m_segment) { + continue; + } - if (!(next1FwdTransitions[j].type == LaneEndTransitionType.Default || - next1FwdTransitions[j].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && next1FwdTransitions[j].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } + if (!(next1FwdTransitions[j].type == LaneEndTransitionType.Default || + next1FwdTransitions[j].type == LaneEndTransitionType.LaneConnection || + (recklessDriver && next1FwdTransitions[j].type == LaneEndTransitionType.Relaxed)) + ) { + continue; + } - if (next1FwdTransitions[j].distance > 1) { + if (next1FwdTransitions[j].distance > 1) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (distance too large)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (distance too large)"); + } #endif - continue; - } + continue; + } - if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next2PathPos.m_segment, next1FwdTransitions[j].laneIndex, next2SegInfo)) { + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next2PathPos.m_segment, next1FwdTransitions[j].laneIndex, next2SegInfo)) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (vehicle restrictions)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (vehicle restrictions)"); + } #endif - continue; - } + continue; + } - if (next3PathPos.m_segment != 0) { - // next2 -> next3 - uint next2FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next1FwdTransitions[j].laneId, !next1FwdTransitions[j].startNode); + if (next3PathPos.m_segment != 0) { + // next2 -> next3 + uint next2FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next1FwdTransitions[j].laneId, !next1FwdTransitions[j].startNode); #if DEBUG - if (next2FwdRoutingIndex < 0 || next2FwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { - Log.Error($"Invalid array index: next2FwdRoutingIndex={next2FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (next1FwdTransitions[j].laneId={next1FwdTransitions[j].laneId}, !next1FwdTransitions[j].startNode={!next1FwdTransitions[j].startNode})"); - } + if (next2FwdRoutingIndex < 0 || next2FwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { + Log.Error($"Invalid array index: next2FwdRoutingIndex={next2FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (next1FwdTransitions[j].laneId={next1FwdTransitions[j].laneId}, !next1FwdTransitions[j].startNode={!next1FwdTransitions[j].startNode})"); + } #endif #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next2 lane id={next1FwdTransitions[j].laneId}, seg.={next1FwdTransitions[j].segmentId}, index={next1FwdTransitions[j].laneIndex}, startNode={!next1FwdTransitions[j].startNode}: {RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex]}"); - } -#endif - if (!RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex].routed) { - continue; - } - LaneTransitionData[] next2FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex].transitions; - - if (next2FwdTransitions == null) { - continue; - } - - bool foundNext2Next3 = false; - for (int k = 0; k < next2FwdTransitions.Length; ++k) { - if (next2FwdTransitions[k].segmentId != next3PathPos.m_segment) { - continue; - } - - if (!(next2FwdTransitions[k].type == LaneEndTransitionType.Default || - next2FwdTransitions[k].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && next2FwdTransitions[k].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } - - if (next2FwdTransitions[k].distance > 1) { -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next2 transition {next2FwdTransitions[k]} (distance too large)"); - } -#endif - continue; - } - - if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next3PathPos.m_segment, next2FwdTransitions[k].laneIndex, next3SegInfo)) { -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next2 transition {next2FwdTransitions[k]} (vehicle restrictions)"); - } -#endif - continue; - } - - if (next4PathPos.m_segment != 0) { - // next3 -> next4 - uint next3FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next2FwdTransitions[k].laneId, !next2FwdTransitions[k].startNode); -#if DEBUG - if (next3FwdRoutingIndex < 0 || next3FwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { - Log.Error($"Invalid array index: next3FwdRoutingIndex={next3FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (next2FwdTransitions[k].laneId={next2FwdTransitions[k].laneId}, !next2FwdTransitions[k].startNode={!next2FwdTransitions[k].startNode})"); - } -#endif - -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next3 lane id={next2FwdTransitions[k].laneId}, seg.={next2FwdTransitions[k].segmentId}, index={next2FwdTransitions[k].laneIndex}, startNode={!next2FwdTransitions[k].startNode}: {RoutingManager.Instance.LaneEndForwardRoutings[next3FwdRoutingIndex]}"); - } -#endif - if (!RoutingManager.Instance.LaneEndForwardRoutings[next3FwdRoutingIndex].routed) { - continue; - } - LaneTransitionData[] next3FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next3FwdRoutingIndex].transitions; - - if (next3FwdTransitions == null) { - continue; - } - - // check if original next4 lane is accessible via the next3 lane - bool foundNext3Next4 = false; - for (int l = 0; l < next3FwdTransitions.Length; ++l) { - if (next3FwdTransitions[l].segmentId != next4PathPos.m_segment) { - continue; - } - - if (!(next3FwdTransitions[l].type == LaneEndTransitionType.Default || - next3FwdTransitions[l].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && next3FwdTransitions[l].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } - - if (next3FwdTransitions[l].distance > 1) { -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next3 transition {next3FwdTransitions[l]} (distance too large)"); - } -#endif - continue; - } - - if (next3FwdTransitions[l].laneIndex == next4PathPos.m_lane) { - // we found a valid routing from [current lane] (currentPathPos) to [next1 lane] (next1Pos), [next2 lane] (next2Pos), [next3 lane] (next3Pos), and [next4 lane] (next4Pos) - - foundNext3Next4 = true; - int totalLaneDist = next1FwdTransitions[j].distance + next2FwdTransitions[k].distance + next3FwdTransitions[l].distance; - if (totalLaneDist < minTotalLaneDist) { - minTotalLaneDist = totalLaneDist; - } -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Found candidate transition with totalLaneDist={totalLaneDist}: {currentLaneId} -> {currentFwdTransitions[i]} -> {next1FwdTransitions[j]} -> {next2FwdTransitions[k]} -> {next3FwdTransitions[l]}"); - } -#endif - break; - } - } // for l - - if (foundNext3Next4) { - foundNext2Next3 = true; - } - } else { - foundNext2Next3 = true; - } - - if (foundNext2Next3) { - mask = POW2MASKS[next2FwdTransitions[k].laneIndex]; - if ((reachableNext3LanesMask & mask) == 0) { - ++numReachableNext3Lanes; - reachableNext3LanesMask |= mask; - } - } - } // for k - - if (foundNext2Next3) { - foundNext1Next2 = true; - } - } else { - foundNext1Next2 = true; - } - - if (foundNext1Next2) { - mask = POW2MASKS[next1FwdTransitions[j].laneIndex]; - if ((reachableNext2LanesMask & mask) == 0) { - ++numReachableNext2Lanes; - reachableNext2LanesMask |= mask; - } - } - } // for j - - if (next3PathPos.m_segment != 0 && !foundNext1Next2) { - // go to next candidate next1 lane - continue; - } - } - - /*mask = POW2MASKS[currentFwdTransitions[i].laneIndex]; - if ((reachableNext1LanesMask & mask) == 0) { - ++numReachableNext1Lanes; - reachableNext1LanesMask |= mask; - }*/ - - // This lane is a valid candidate. - - //bool next1StartNode = next1PathPos.m_offset < 128; - //ushort next1TransitNode = 0; - //Services.NetService.ProcessSegment(next1PathPos.m_segment, delegate (ushort next1SegId, ref NetSegment next1Seg) { - // next1TransitNode = next1StartNode ? next1Seg.m_startNode : next1Seg.m_endNode; - // return true; - //}); - - //bool next1TransitNodeIsJunction = false; - //Services.NetService.ProcessNode(next1TransitNode, delegate (ushort nId, ref NetNode node) { - // next1TransitNodeIsJunction = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; - // return true; - //}); - - /* - * Check if next1 lane is clear - */ -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for traffic on next1 lane id={currentFwdTransitions[i].laneId}."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next2 lane id={next1FwdTransitions[j].laneId}, seg.={next1FwdTransitions[j].segmentId}, index={next1FwdTransitions[j].laneIndex}, startNode={!next1FwdTransitions[j].startNode}: {RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex]}"); + } #endif + if (!RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex].routed) { + continue; + } + LaneTransitionData[] next2FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex].transitions; - bool laneChange = currentFwdTransitions[i].distance != 0; - /*bool next1LaneClear = true; - if (laneChange) { - // check for traffic on next1 lane - float reservedSpace = 0; - Services.NetService.ProcessLane(currentFwdTransitions[i].laneId, delegate (uint next1LaneId, ref NetLane next1Lane) { - reservedSpace = next1Lane.GetReservedSpace(); - return true; - }); + if (next2FwdTransitions == null) { + continue; + } - if (currentFwdTransitions[i].laneIndex == next1PathPos.m_lane) { - reservedSpace -= vehicleLength; - } - - next1LaneClear = reservedSpace <= (recklessDriver ? conf.AltLaneSelectionMaxRecklessReservedSpace : conf.AltLaneSelectionMaxReservedSpace); - } + bool foundNext2Next3 = false; + for (int k = 0; k < next2FwdTransitions.Length; ++k) { + if (next2FwdTransitions[k].segmentId != next3PathPos.m_segment) { + continue; + } - if (foundClearFwdLane && !next1LaneClear) { - continue; - }*/ + if (!(next2FwdTransitions[k].type == LaneEndTransitionType.Default || + next2FwdTransitions[k].type == LaneEndTransitionType.LaneConnection || + (recklessDriver && next2FwdTransitions[k].type == LaneEndTransitionType.Relaxed)) + ) { + continue; + } - /* - * Check traffic on the lanes in front of the candidate lane in order to prevent vehicles from backing up traffic - */ - bool prevLanesClear = true; - if (laneChange) { - uint next1BackRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, currentFwdTransitions[i].startNode); + if (next2FwdTransitions[k].distance > 1) { #if DEBUG - if (next1BackRoutingIndex < 0 || next1BackRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { - Log.Error($"Invalid array index: next1BackRoutingIndex={next1BackRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentFwdTransitions[i].laneId={currentFwdTransitions[i].laneId}, currentFwdTransitions[i].startNode={currentFwdTransitions[i].startNode})"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next2 transition {next2FwdTransitions[k]} (distance too large)"); + } #endif - if (!RoutingManager.Instance.LaneEndBackwardRoutings[next1BackRoutingIndex].routed) { - continue; - } - LaneTransitionData[] next1BackTransitions = RoutingManager.Instance.LaneEndBackwardRoutings[next1BackRoutingIndex].transitions; + continue; + } - if (next1BackTransitions == null) { - continue; - } + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next3PathPos.m_segment, next2FwdTransitions[k].laneIndex, next3SegInfo)) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next2 transition {next2FwdTransitions[k]} (vehicle restrictions)"); + } +#endif + continue; + } + + if (next4PathPos.m_segment != 0) { + // next3 -> next4 + uint next3FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next2FwdTransitions[k].laneId, !next2FwdTransitions[k].startNode); +#if DEBUG + if (next3FwdRoutingIndex < 0 || next3FwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { + Log.Error($"Invalid array index: next3FwdRoutingIndex={next3FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (next2FwdTransitions[k].laneId={next2FwdTransitions[k].laneId}, !next2FwdTransitions[k].startNode={!next2FwdTransitions[k].startNode})"); + } +#endif + +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next3 lane id={next2FwdTransitions[k].laneId}, seg.={next2FwdTransitions[k].segmentId}, index={next2FwdTransitions[k].laneIndex}, startNode={!next2FwdTransitions[k].startNode}: {RoutingManager.Instance.LaneEndForwardRoutings[next3FwdRoutingIndex]}"); + } +#endif + if (!RoutingManager.Instance.LaneEndForwardRoutings[next3FwdRoutingIndex].routed) { + continue; + } + LaneTransitionData[] next3FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next3FwdRoutingIndex].transitions; + + if (next3FwdTransitions == null) { + continue; + } + + // check if original next4 lane is accessible via the next3 lane + bool foundNext3Next4 = false; + for (int l = 0; l < next3FwdTransitions.Length; ++l) { + if (next3FwdTransitions[l].segmentId != next4PathPos.m_segment) { + continue; + } + + if (!(next3FwdTransitions[l].type == LaneEndTransitionType.Default || + next3FwdTransitions[l].type == LaneEndTransitionType.LaneConnection || + (recklessDriver && next3FwdTransitions[l].type == LaneEndTransitionType.Relaxed)) + ) { + continue; + } + + if (next3FwdTransitions[l].distance > 1) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next3 transition {next3FwdTransitions[l]} (distance too large)"); + } +#endif + continue; + } + + if (next3FwdTransitions[l].laneIndex == next4PathPos.m_lane) { + // we found a valid routing from [current lane] (currentPathPos) to [next1 lane] (next1Pos), [next2 lane] (next2Pos), [next3 lane] (next3Pos), and [next4 lane] (next4Pos) + + foundNext3Next4 = true; + int totalLaneDist = next1FwdTransitions[j].distance + next2FwdTransitions[k].distance + next3FwdTransitions[l].distance; + if (totalLaneDist < minTotalLaneDist) { + minTotalLaneDist = totalLaneDist; + } +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Found candidate transition with totalLaneDist={totalLaneDist}: {currentLaneId} -> {currentFwdTransitions[i]} -> {next1FwdTransitions[j]} -> {next2FwdTransitions[k]} -> {next3FwdTransitions[l]}"); + } +#endif + break; + } + } // for l + + if (foundNext3Next4) { + foundNext2Next3 = true; + } + } else { + foundNext2Next3 = true; + } + + if (foundNext2Next3) { + mask = POW2MASKS[next2FwdTransitions[k].laneIndex]; + if ((reachableNext3LanesMask & mask) == 0) { + ++numReachableNext3Lanes; + reachableNext3LanesMask |= mask; + } + } + } // for k + + if (foundNext2Next3) { + foundNext1Next2 = true; + } + } else { + foundNext1Next2 = true; + } + + if (foundNext1Next2) { + mask = POW2MASKS[next1FwdTransitions[j].laneIndex]; + if ((reachableNext2LanesMask & mask) == 0) { + ++numReachableNext2Lanes; + reachableNext2LanesMask |= mask; + } + } + } // for j + + if (next3PathPos.m_segment != 0 && !foundNext1Next2) { + // go to next candidate next1 lane + continue; + } + } + + /*mask = POW2MASKS[currentFwdTransitions[i].laneIndex]; + if ((reachableNext1LanesMask & mask) == 0) { + ++numReachableNext1Lanes; + reachableNext1LanesMask |= mask; + }*/ + + // This lane is a valid candidate. + + //bool next1StartNode = next1PathPos.m_offset < 128; + //ushort next1TransitNode = 0; + //Services.NetService.ProcessSegment(next1PathPos.m_segment, delegate (ushort next1SegId, ref NetSegment next1Seg) { + // next1TransitNode = next1StartNode ? next1Seg.m_startNode : next1Seg.m_endNode; + // return true; + //}); + + //bool next1TransitNodeIsJunction = false; + //Services.NetService.ProcessNode(next1TransitNode, delegate (ushort nId, ref NetNode node) { + // next1TransitNodeIsJunction = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; + // return true; + //}); + + /* + * Check if next1 lane is clear + */ +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for traffic on next1 lane id={currentFwdTransitions[i].laneId}."); + } +#endif + + bool laneChange = currentFwdTransitions[i].distance != 0; + /*bool next1LaneClear = true; + if (laneChange) { + // check for traffic on next1 lane + float reservedSpace = 0; + Services.NetService.ProcessLane(currentFwdTransitions[i].laneId, delegate (uint next1LaneId, ref NetLane next1Lane) { + reservedSpace = next1Lane.GetReservedSpace(); + return true; + }); + + if (currentFwdTransitions[i].laneIndex == next1PathPos.m_lane) { + reservedSpace -= vehicleLength; + } + + next1LaneClear = reservedSpace <= (recklessDriver ? conf.AltLaneSelectionMaxRecklessReservedSpace : conf.AltLaneSelectionMaxReservedSpace); + } + + if (foundClearFwdLane && !next1LaneClear) { + continue; + }*/ + + /* + * Check traffic on the lanes in front of the candidate lane in order to prevent vehicles from backing up traffic + */ + bool prevLanesClear = true; + if (laneChange) { + uint next1BackRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, currentFwdTransitions[i].startNode); +#if DEBUG + if (next1BackRoutingIndex < 0 || next1BackRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { + Log.Error($"Invalid array index: next1BackRoutingIndex={next1BackRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentFwdTransitions[i].laneId={currentFwdTransitions[i].laneId}, currentFwdTransitions[i].startNode={currentFwdTransitions[i].startNode})"); + } +#endif + if (!RoutingManager.Instance.LaneEndBackwardRoutings[next1BackRoutingIndex].routed) { + continue; + } + LaneTransitionData[] next1BackTransitions = RoutingManager.Instance.LaneEndBackwardRoutings[next1BackRoutingIndex].transitions; + + if (next1BackTransitions == null) { + continue; + } - for (int j = 0; j < next1BackTransitions.Length; ++j) { - if (next1BackTransitions[j].segmentId != currentPathPos.m_segment || - next1BackTransitions[j].laneIndex == currentPathPos.m_lane) { - continue; - } + for (int j = 0; j < next1BackTransitions.Length; ++j) { + if (next1BackTransitions[j].segmentId != currentPathPos.m_segment || + next1BackTransitions[j].laneIndex == currentPathPos.m_lane) { + continue; + } - if (!(next1BackTransitions[j].type == LaneEndTransitionType.Default || - next1BackTransitions[j].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && next1BackTransitions[j].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } + if (!(next1BackTransitions[j].type == LaneEndTransitionType.Default || + next1BackTransitions[j].type == LaneEndTransitionType.LaneConnection || + (recklessDriver && next1BackTransitions[j].type == LaneEndTransitionType.Relaxed)) + ) { + continue; + } - if (next1BackTransitions[j].distance > 1) { -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 backward transition {next1BackTransitions[j]} (distance too large)"); - } -#endif - continue; - } + if (next1BackTransitions[j].distance > 1) { +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 backward transition {next1BackTransitions[j]} (distance too large)"); + } +#endif + continue; + } #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for upcoming traffic in front of next1 lane id={currentFwdTransitions[i].laneId}. Checking back transition {next1BackTransitions[j]}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for upcoming traffic in front of next1 lane id={currentFwdTransitions[i].laneId}. Checking back transition {next1BackTransitions[j]}"); + } #endif - Services.NetService.ProcessLane(next1BackTransitions[j].laneId, delegate (uint prevLaneId, ref NetLane prevLane) { - prevLanesClear = prevLane.GetReservedSpace() <= maxReservedSpace; - return true; - }); + Services.NetService.ProcessLane(next1BackTransitions[j].laneId, delegate (uint prevLaneId, ref NetLane prevLane) { + prevLanesClear = prevLane.GetReservedSpace() <= maxReservedSpace; + return true; + }); - if (!prevLanesClear) { + if (!prevLanesClear) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is not clear!"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is not clear!"); + } #endif - break; - } else { + break; + } else { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is clear!"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is clear!"); + } #endif - } - } - } + } + } + } #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for coming up traffic in front of next1 lane. prevLanesClear={prevLanesClear}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for coming up traffic in front of next1 lane. prevLanesClear={prevLanesClear}"); + } #endif - if (/*foundClearBackLane*/foundSafeLaneChange && !prevLanesClear) { - continue; - } + if (/*foundClearBackLane*/foundSafeLaneChange && !prevLanesClear) { + continue; + } - // calculate lane metric + // calculate lane metric #if DEBUG - if (currentFwdTransitions[i].laneIndex < 0 || currentFwdTransitions[i].laneIndex >= next1SegInfo.m_lanes.Length) { - Log.Error($"Invalid array index: currentFwdTransitions[i].laneIndex={currentFwdTransitions[i].laneIndex}, next1SegInfo.m_lanes.Length={next1SegInfo.m_lanes.Length}"); - } + if (currentFwdTransitions[i].laneIndex < 0 || currentFwdTransitions[i].laneIndex >= next1SegInfo.m_lanes.Length) { + Log.Error($"Invalid array index: currentFwdTransitions[i].laneIndex={currentFwdTransitions[i].laneIndex}, next1SegInfo.m_lanes.Length={next1SegInfo.m_lanes.Length}"); + } #endif - NetInfo.Lane next1LaneInfo = next1SegInfo.m_lanes[currentFwdTransitions[i].laneIndex]; - float next1MaxSpeed = SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); - float targetSpeed = Math.Min(vehicleMaxSpeed, ApplyRealisticSpeeds(next1MaxSpeed, vehicleId, ref vehicleState, vehicleInfo)); + NetInfo.Lane next1LaneInfo = next1SegInfo.m_lanes[currentFwdTransitions[i].laneIndex]; + float next1MaxSpeed = SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); + float targetSpeed = Math.Min(vehicleMaxSpeed, ApplyRealisticSpeeds(next1MaxSpeed, vehicleId, ref vehicleState, vehicleInfo)); - ushort meanSpeed = TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); + ushort meanSpeed = TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); - float relMeanSpeedInPercent = meanSpeed / (TrafficMeasurementManager.REF_REL_SPEED / TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR); - float randSpeed = 0f; - if (vehicleState.laneSpeedRandInterval > 0) { - randSpeed = Services.SimulationService.Randomizer.Int32((uint)vehicleState.laneSpeedRandInterval + 1u) - vehicleState.laneSpeedRandInterval / 2f; - relMeanSpeedInPercent += randSpeed; - } + float relMeanSpeedInPercent = meanSpeed / (TrafficMeasurementManager.REF_REL_SPEED / TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR); + float randSpeed = 0f; + if (vehicleState.laneSpeedRandInterval > 0) { + randSpeed = Services.SimulationService.Randomizer.Int32((uint)vehicleState.laneSpeedRandInterval + 1u) - vehicleState.laneSpeedRandInterval / 2f; + relMeanSpeedInPercent += randSpeed; + } - float relMeanSpeed = relMeanSpeedInPercent / (float)TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR; - float next1MeanSpeed = relMeanSpeed * next1MaxSpeed; + float relMeanSpeed = relMeanSpeedInPercent / (float)TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR; + float next1MeanSpeed = relMeanSpeed * next1MaxSpeed; - /*if ( + /*if ( #if DEBUG - conf.Debug.Switches[19] && + conf.Debug.Switches[19] && #endif - next1LaneInfo.m_similarLaneCount > 1) { - float relLaneInnerIndex = ((float)RoutingManager.Instance.CalcOuterSimilarLaneIndex(next1LaneInfo) / (float)next1LaneInfo.m_similarLaneCount); - float rightObligationFactor = conf.AltLaneSelectionMostOuterLaneSpeedFactor + (conf.AltLaneSelectionMostInnerLaneSpeedFactor - conf.AltLaneSelectionMostOuterLaneSpeedFactor) * relLaneInnerIndex; + next1LaneInfo.m_similarLaneCount > 1) { + float relLaneInnerIndex = ((float)RoutingManager.Instance.CalcOuterSimilarLaneIndex(next1LaneInfo) / (float)next1LaneInfo.m_similarLaneCount); + float rightObligationFactor = conf.AltLaneSelectionMostOuterLaneSpeedFactor + (conf.AltLaneSelectionMostInnerLaneSpeedFactor - conf.AltLaneSelectionMostOuterLaneSpeedFactor) * relLaneInnerIndex; #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Applying obligation factor to next1 lane {currentFwdTransitions[i].laneId}: relLaneInnerIndex={relLaneInnerIndex}, rightObligationFactor={rightObligationFactor}, next1MaxSpeed={next1MaxSpeed}, relMeanSpeedInPercent={relMeanSpeedInPercent}, randSpeed={randSpeed}, next1MeanSpeed={next1MeanSpeed} => new next1MeanSpeed={Mathf.Max(rightObligationFactor * next1MaxSpeed, next1MeanSpeed)}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Applying obligation factor to next1 lane {currentFwdTransitions[i].laneId}: relLaneInnerIndex={relLaneInnerIndex}, rightObligationFactor={rightObligationFactor}, next1MaxSpeed={next1MaxSpeed}, relMeanSpeedInPercent={relMeanSpeedInPercent}, randSpeed={randSpeed}, next1MeanSpeed={next1MeanSpeed} => new next1MeanSpeed={Mathf.Max(rightObligationFactor * next1MaxSpeed, next1MeanSpeed)}"); + } #endif - next1MeanSpeed = Mathf.Min(rightObligationFactor * next1MaxSpeed, next1MeanSpeed); - }*/ + next1MeanSpeed = Mathf.Min(rightObligationFactor * next1MaxSpeed, next1MeanSpeed); + }*/ - float speedDiff = next1MeanSpeed - targetSpeed; // > 0: lane is faster than vehicle would go. < 0: vehicle could go faster than this lane allows + float speedDiff = next1MeanSpeed - targetSpeed; // > 0: lane is faster than vehicle would go. < 0: vehicle could go faster than this lane allows #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Calculated metric for next1 lane {currentFwdTransitions[i].laneId}: next1MaxSpeed={next1MaxSpeed} next1MeanSpeed={next1MeanSpeed} targetSpeed={targetSpeed} speedDiff={speedDiff} bestSpeedDiff={bestOptSpeedDiff} bestStaySpeedDiff={bestStaySpeedDiff}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Calculated metric for next1 lane {currentFwdTransitions[i].laneId}: next1MaxSpeed={next1MaxSpeed} next1MeanSpeed={next1MeanSpeed} targetSpeed={targetSpeed} speedDiff={speedDiff} bestSpeedDiff={bestOptSpeedDiff} bestStaySpeedDiff={bestStaySpeedDiff}"); + } #endif - if (!laneChange) { - if ((float.IsInfinity(bestStaySpeedDiff) || - (bestStaySpeedDiff < 0 && speedDiff > bestStaySpeedDiff) || - (bestStaySpeedDiff > 0 && speedDiff < bestStaySpeedDiff && speedDiff >= 0)) - ) { - bestStaySpeedDiff = speedDiff; - bestStayNext1LaneIndex = currentFwdTransitions[i].laneIndex; - bestStayMeanSpeed = next1MeanSpeed; - bestStayTotalLaneDist = minTotalLaneDist; - } - } else { - //bool foundFirstClearFwdLane = laneChange && !foundClearFwdLane && next1LaneClear; - //bool foundFirstClearBackLane = laneChange && !foundClearBackLane && prevLanesClear; - bool foundFirstSafeLaneChange = !foundSafeLaneChange && /*next1LaneClear &&*/ prevLanesClear; - if (/*(foundFirstClearFwdLane && !foundClearBackLane) || + if (!laneChange) { + if ((float.IsInfinity(bestStaySpeedDiff) || + (bestStaySpeedDiff < 0 && speedDiff > bestStaySpeedDiff) || + (bestStaySpeedDiff > 0 && speedDiff < bestStaySpeedDiff && speedDiff >= 0)) + ) { + bestStaySpeedDiff = speedDiff; + bestStayNext1LaneIndex = currentFwdTransitions[i].laneIndex; + bestStayMeanSpeed = next1MeanSpeed; + bestStayTotalLaneDist = minTotalLaneDist; + } + } else { + //bool foundFirstClearFwdLane = laneChange && !foundClearFwdLane && next1LaneClear; + //bool foundFirstClearBackLane = laneChange && !foundClearBackLane && prevLanesClear; + bool foundFirstSafeLaneChange = !foundSafeLaneChange && /*next1LaneClear &&*/ prevLanesClear; + if (/*(foundFirstClearFwdLane && !foundClearBackLane) || (foundFirstClearBackLane && !foundClearFwdLane) ||*/ - foundFirstSafeLaneChange || - float.IsInfinity(bestOptSpeedDiff) || - (bestOptSpeedDiff < 0 && speedDiff > bestOptSpeedDiff) || - (bestOptSpeedDiff > 0 && speedDiff < bestOptSpeedDiff && speedDiff >= 0)) { - bestOptSpeedDiff = speedDiff; - bestOptNext1LaneIndex = currentFwdTransitions[i].laneIndex; - bestOptMeanSpeed = next1MeanSpeed; - bestOptTotalLaneDist = minTotalLaneDist; - } + foundFirstSafeLaneChange || + float.IsInfinity(bestOptSpeedDiff) || + (bestOptSpeedDiff < 0 && speedDiff > bestOptSpeedDiff) || + (bestOptSpeedDiff > 0 && speedDiff < bestOptSpeedDiff && speedDiff >= 0)) { + bestOptSpeedDiff = speedDiff; + bestOptNext1LaneIndex = currentFwdTransitions[i].laneIndex; + bestOptMeanSpeed = next1MeanSpeed; + bestOptTotalLaneDist = minTotalLaneDist; + } - /*if (foundFirstClearBackLane) { - foundClearBackLane = true; - } + /*if (foundFirstClearBackLane) { + foundClearBackLane = true; + } - if (foundFirstClearFwdLane) { - foundClearFwdLane = true; - }*/ + if (foundFirstClearFwdLane) { + foundClearFwdLane = true; + }*/ - if (foundFirstSafeLaneChange) { - foundSafeLaneChange = true; - } - } - } // for i + if (foundFirstSafeLaneChange) { + foundSafeLaneChange = true; + } + } + } // for i #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): best lane index: {bestOptNext1LaneIndex}, best stay lane index: {bestStayNext1LaneIndex}, path lane index: {next1PathPos.m_lane})\nbest speed diff: {bestOptSpeedDiff}, best stay speed diff: {bestStaySpeedDiff}\nfoundClearBackLane=XXfoundClearBackLaneXX, foundClearFwdLane=XXfoundClearFwdLaneXX, foundSafeLaneChange={foundSafeLaneChange}\nbestMeanSpeed={bestOptMeanSpeed}, bestStayMeanSpeed={bestStayMeanSpeed}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): best lane index: {bestOptNext1LaneIndex}, best stay lane index: {bestStayNext1LaneIndex}, path lane index: {next1PathPos.m_lane})\nbest speed diff: {bestOptSpeedDiff}, best stay speed diff: {bestStaySpeedDiff}\nfoundClearBackLane=XXfoundClearBackLaneXX, foundClearFwdLane=XXfoundClearFwdLaneXX, foundSafeLaneChange={foundSafeLaneChange}\nbestMeanSpeed={bestOptMeanSpeed}, bestStayMeanSpeed={bestStayMeanSpeed}"); + } #endif - if (float.IsInfinity(bestStaySpeedDiff)) { - // no continuous lane found + if (float.IsInfinity(bestStaySpeedDiff)) { + // no continuous lane found #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no continuous lane found -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no continuous lane found -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); + } #endif - return bestOptNext1LaneIndex; - } + return bestOptNext1LaneIndex; + } - if (float.IsInfinity(bestOptSpeedDiff)) { - // no lane change found + if (float.IsInfinity(bestOptSpeedDiff)) { + // no lane change found #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no lane change found -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no lane change found -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #endif - return bestStayNext1LaneIndex; - } + return bestStayNext1LaneIndex; + } - // decide if vehicle should stay or change + // decide if vehicle should stay or change - // vanishing lane change opportunity detection - int vehSel = vehicleId % 12; + // vanishing lane change opportunity detection + int vehSel = vehicleId % 12; #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vehMod4={vehSel} numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vehMod4={vehSel} numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}"); + } #endif - if ((numReachableNext3Lanes == 1 && vehSel <= 5) || // 50% of all vehicles will change lanes 3 segments in front - (numReachableNext2Lanes == 1 && vehSel <= 9) // 33% of all vehicles will change lanes 2 segments in front, 16.67% will change at the last opportunity - ) { - // vehicle must reach a certain lane since lane changing opportunities will vanish + if ((numReachableNext3Lanes == 1 && vehSel <= 5) || // 50% of all vehicles will change lanes 3 segments in front + (numReachableNext2Lanes == 1 && vehSel <= 9) // 33% of all vehicles will change lanes 2 segments in front, 16.67% will change at the last opportunity + ) { + // vehicle must reach a certain lane since lane changing opportunities will vanish #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vanishing lane change opportunities detected: numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}, vehSel={vehSel}, bestOptTotalLaneDist={bestOptTotalLaneDist}, bestStayTotalLaneDist={bestStayTotalLaneDist}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vanishing lane change opportunities detected: numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}, vehSel={vehSel}, bestOptTotalLaneDist={bestOptTotalLaneDist}, bestStayTotalLaneDist={bestStayTotalLaneDist}"); + } #endif - if (bestOptTotalLaneDist < bestStayTotalLaneDist) { + if (bestOptTotalLaneDist < bestStayTotalLaneDist) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestOptTotalLaneDist={bestOptTotalLaneDist}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestOptTotalLaneDist={bestOptTotalLaneDist}"); + } #endif - return bestOptNext1LaneIndex; - } else { + return bestOptNext1LaneIndex; + } else { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestStayTotalLaneDist={bestStayTotalLaneDist}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestStayTotalLaneDist={bestStayTotalLaneDist}"); + } #endif - return bestStayNext1LaneIndex; - } - } + return bestStayNext1LaneIndex; + } + } - if (bestStaySpeedDiff == 0 || bestOptMeanSpeed < 0.1f) { - /* - * edge cases: - * (1) continuous lane is super optimal - * (2) best mean speed is near zero - */ + if (bestStaySpeedDiff == 0 || bestOptMeanSpeed < 0.1f) { + /* + * edge cases: + * (1) continuous lane is super optimal + * (2) best mean speed is near zero + */ #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> edge case: continuous lane is optimal ({bestStaySpeedDiff == 0}) / best mean speed is near zero ({bestOptMeanSpeed < 0.1f}) -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> edge case: continuous lane is optimal ({bestStaySpeedDiff == 0}) / best mean speed is near zero ({bestOptMeanSpeed < 0.1f}) -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #endif - return bestStayNext1LaneIndex; - } + return bestStayNext1LaneIndex; + } - if (bestStayTotalLaneDist != bestOptTotalLaneDist && Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist) > vehicleState.maxOptLaneChanges) { - /* - * best route contains more lane changes than allowed: choose lane with the least number of future lane changes - */ + if (bestStayTotalLaneDist != bestOptTotalLaneDist && Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist) > vehicleState.maxOptLaneChanges) { + /* + * best route contains more lane changes than allowed: choose lane with the least number of future lane changes + */ #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): maximum best total lane distance = {Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist)} > AltLaneSelectionMaxOptLaneChanges"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): maximum best total lane distance = {Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist)} > AltLaneSelectionMaxOptLaneChanges"); + } #endif - if (bestOptTotalLaneDist < bestStayTotalLaneDist) { + if (bestOptTotalLaneDist < bestStayTotalLaneDist) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting lane change option for minimizing number of future lane changes -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting lane change option for minimizing number of future lane changes -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); + } #endif - return bestOptNext1LaneIndex; - } else { + return bestOptNext1LaneIndex; + } else { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting stay option for minimizing number of future lane changes -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting stay option for minimizing number of future lane changes -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #endif - return bestStayNext1LaneIndex; - } - } - - if (bestStaySpeedDiff < 0 && bestOptSpeedDiff > bestStaySpeedDiff) { - // found a lane change that improves vehicle speed - //float improvement = 100f * ((bestOptSpeedDiff - bestStaySpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); - var speedDiff = Mathf.Abs(bestOptMeanSpeed - vehicleCurSpeed); - var optImprovementSpeed = SpeedLimit.ToKmphRounded(bestOptSpeedDiff - bestStaySpeedDiff); -#if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): " + - $"a lane change for speed improvement is possible. " + - $"optImprovementInKmH={optImprovementSpeed} km/h speedDiff={speedDiff} " + - $"(bestOptMeanSpeed={bestOptMeanSpeed}, vehicleCurVelocity={vehicleCurSpeed}, " + - $"foundSafeLaneChange={foundSafeLaneChange})"); - } + return bestStayNext1LaneIndex; + } + } + + if (bestStaySpeedDiff < 0 && bestOptSpeedDiff > bestStaySpeedDiff) { + // found a lane change that improves vehicle speed + //float improvement = 100f * ((bestOptSpeedDiff - bestStaySpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); + var speedDiff = Mathf.Abs(bestOptMeanSpeed - vehicleCurSpeed); + var optImprovementSpeed = SpeedLimit.ToKmphRounded(bestOptSpeedDiff - bestStaySpeedDiff); +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): " + + $"a lane change for speed improvement is possible. " + + $"optImprovementInKmH={optImprovementSpeed} km/h speedDiff={speedDiff} " + + $"(bestOptMeanSpeed={bestOptMeanSpeed}, vehicleCurVelocity={vehicleCurSpeed}, " + + $"foundSafeLaneChange={foundSafeLaneChange})"); + } #endif - if (optImprovementSpeed >= vehicleState.minSafeSpeedImprovement && - (foundSafeLaneChange || (speedDiff <= vehicleState.maxUnsafeSpeedDiff)) - ) { - // speed improvement is significant + if (optImprovementSpeed >= vehicleState.minSafeSpeedImprovement && + (foundSafeLaneChange || (speedDiff <= vehicleState.maxUnsafeSpeedDiff)) + ) { + // speed improvement is significant #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a faster lane to change to and speed improvement is significant -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex} (foundSafeLaneChange={foundSafeLaneChange}, speedDiff={speedDiff})"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a faster lane to change to and speed improvement is significant -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex} (foundSafeLaneChange={foundSafeLaneChange}, speedDiff={speedDiff})"); + } #endif - return bestOptNext1LaneIndex; - } + return bestOptNext1LaneIndex; + } - // insufficient improvement + // insufficient improvement #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a faster lane to change to but speed improvement is NOT significant OR lane change is unsafe -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex} (foundSafeLaneChange={foundSafeLaneChange})"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a faster lane to change to but speed improvement is NOT significant OR lane change is unsafe -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex} (foundSafeLaneChange={foundSafeLaneChange})"); + } #endif - return bestStayNext1LaneIndex; - } else if (!recklessDriver && foundSafeLaneChange && bestStaySpeedDiff > 0 && bestOptSpeedDiff < bestStaySpeedDiff && bestOptSpeedDiff >= 0) { - // found a lane change that allows faster vehicles to overtake - float optimization = 100f * ((bestStaySpeedDiff - bestOptSpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); + return bestStayNext1LaneIndex; + } else if (!recklessDriver && foundSafeLaneChange && bestStaySpeedDiff > 0 && bestOptSpeedDiff < bestStaySpeedDiff && bestOptSpeedDiff >= 0) { + // found a lane change that allows faster vehicles to overtake + float optimization = 100f * ((bestStaySpeedDiff - bestOptSpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): found a lane change that optimizes overall traffic. optimization={optimization}%"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): found a lane change that optimizes overall traffic. optimization={optimization}%"); + } #endif - if (optimization >= vehicleState.minSafeTrafficImprovement) { - // traffic optimization is significant + if (optimization >= vehicleState.minSafeTrafficImprovement) { + // traffic optimization is significant #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic and traffic optimization is significant -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic and traffic optimization is significant -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); + } #endif - return bestOptNext1LaneIndex; - } + return bestOptNext1LaneIndex; + } - // insufficient optimization + // insufficient optimization #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic but optimization is NOT significant -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic but optimization is NOT significant -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #endif - return bestOptNext1LaneIndex; - } + return bestOptNext1LaneIndex; + } - // suboptimal safe lane change + // suboptimal safe lane change #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> suboptimal safe lane change detected -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> suboptimal safe lane change detected -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #endif - return bestStayNext1LaneIndex; - } catch (Exception e) { - Log.Error($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exception occurred: {e}"); - } - return next1PathPos.m_lane; - } + return bestStayNext1LaneIndex; + } catch (Exception e) { + Log.Error($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exception occurred: {e}"); + } + return next1PathPos.m_lane; + } - public int FindBestEmergencyLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position nextPathPos, NetInfo nextSegInfo) { - try { - GlobalConfig conf = GlobalConfig.Instance; + public int FindBestEmergencyLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position nextPathPos, NetInfo nextSegInfo) { + try { + GlobalConfig conf = GlobalConfig.Instance; #if DEBUG - bool debug = false; - if (conf.Debug.Switches[17]) { - ushort nodeId = Services.NetService.GetSegmentNodeId(currentPathPos.m_segment, currentPathPos.m_offset < 128); - debug = (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId) && (conf.Debug.NodeId == 0 || conf.Debug.NodeId == nodeId); - } + bool debug = false; + if (conf.Debug.Switches[17]) { + ushort nodeId = Services.NetService.GetSegmentNodeId(currentPathPos.m_segment, currentPathPos.m_offset < 128); + debug = (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId) && (conf.Debug.NodeId == 0 || conf.Debug.NodeId == nodeId); + } - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): currentLaneId={currentLaneId}, currentPathPos=[seg={currentPathPos.m_segment}, lane={currentPathPos.m_lane}, off={currentPathPos.m_offset}] nextPathPos=[seg={nextPathPos.m_segment}, lane={nextPathPos.m_lane}, off={nextPathPos.m_offset}]"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): currentLaneId={currentLaneId}, currentPathPos=[seg={currentPathPos.m_segment}, lane={currentPathPos.m_lane}, off={currentPathPos.m_offset}] nextPathPos=[seg={nextPathPos.m_segment}, lane={nextPathPos.m_lane}, off={nextPathPos.m_offset}]"); + } #endif - // cur -> next - float curPosition = 0f; - if (currentPathPos.m_lane < currentSegInfo.m_lanes.Length) { - curPosition = currentSegInfo.m_lanes[currentPathPos.m_lane].m_position; - } - float vehicleLength = 1f + vehicleState.totalLength; - bool startNode = currentPathPos.m_offset < 128; - uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); + // cur -> next + float curPosition = 0f; + if (currentPathPos.m_lane < currentSegInfo.m_lanes.Length) { + curPosition = currentSegInfo.m_lanes[currentPathPos.m_lane].m_position; + } + float vehicleLength = 1f + vehicleState.totalLength; + bool startNode = currentPathPos.m_offset < 128; + uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); #if DEBUG - if (currentFwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { - Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Invalid array index: currentFwdRoutingIndex={currentFwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentLaneId={currentLaneId}, startNode={startNode})"); - } + if (currentFwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { + Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Invalid array index: currentFwdRoutingIndex={currentFwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentLaneId={currentLaneId}, startNode={startNode})"); + } #endif - if (!RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].routed) { + if (!RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].routed) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward routing for next path position available."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward routing for next path position available."); + } #endif - return nextPathPos.m_lane; - } + return nextPathPos.m_lane; + } - LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].transitions; + LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].transitions; - if (currentFwdTransitions == null) { + if (currentFwdTransitions == null) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); + } #endif - return nextPathPos.m_lane; - } + return nextPathPos.m_lane; + } #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Starting lane-finding algorithm now. vehicleLength={vehicleLength}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Starting lane-finding algorithm now. vehicleLength={vehicleLength}"); + } #endif - float minCost = float.MaxValue; - byte bestNextLaneIndex = nextPathPos.m_lane; - for (int i = 0; i < currentFwdTransitions.Length; ++i) { - if (currentFwdTransitions[i].segmentId != nextPathPos.m_segment) { - continue; - } + float minCost = float.MaxValue; + byte bestNextLaneIndex = nextPathPos.m_lane; + for (int i = 0; i < currentFwdTransitions.Length; ++i) { + if (currentFwdTransitions[i].segmentId != nextPathPos.m_segment) { + continue; + } - if (!( - currentFwdTransitions[i].type == LaneEndTransitionType.Default || - currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || - currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed - )) { - continue; - } + if (!( + currentFwdTransitions[i].type == LaneEndTransitionType.Default || + currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || + currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed + )) { + continue; + } - if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, nextPathPos.m_segment, currentFwdTransitions[i].laneIndex, nextSegInfo)) { + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, nextPathPos.m_segment, currentFwdTransitions[i].laneIndex, nextSegInfo)) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); + } #endif - continue; - } + continue; + } - NetInfo.Lane nextLaneInfo = nextSegInfo.m_lanes[currentFwdTransitions[i].laneIndex]; + NetInfo.Lane nextLaneInfo = nextSegInfo.m_lanes[currentFwdTransitions[i].laneIndex]; - /* - * Check reserved space on next lane - */ + /* + * Check reserved space on next lane + */ #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Checking for traffic on next lane id={currentFwdTransitions[i].laneId}."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Checking for traffic on next lane id={currentFwdTransitions[i].laneId}."); + } #endif - Services.NetService.ProcessLane(currentFwdTransitions[i].laneId, (uint nextLaneId, ref NetLane nextLane) => { - // similar to stock code in VehicleAI.FindBestLane - float cost = nextLane.GetReservedSpace(); - if (currentFwdTransitions[i].laneIndex == nextPathPos.m_lane) { - cost -= vehicleLength; - } + Services.NetService.ProcessLane(currentFwdTransitions[i].laneId, (uint nextLaneId, ref NetLane nextLane) => { + // similar to stock code in VehicleAI.FindBestLane + float cost = nextLane.GetReservedSpace(); + if (currentFwdTransitions[i].laneIndex == nextPathPos.m_lane) { + cost -= vehicleLength; + } - cost += Mathf.Abs(curPosition - nextLaneInfo.m_position) * 0.1f; + cost += Mathf.Abs(curPosition - nextLaneInfo.m_position) * 0.1f; - if (cost < minCost) { - minCost = cost; - bestNextLaneIndex = currentFwdTransitions[i].laneIndex; + if (cost < minCost) { + minCost = cost; + bestNextLaneIndex = currentFwdTransitions[i].laneIndex; #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Found better lane: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Found better lane: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); + } #endif - } - return true; - }); - } // for each forward transition + } + return true; + }); + } // for each forward transition #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Best lane identified: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Best lane identified: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); + } #endif - return bestNextLaneIndex; - } catch (Exception e) { - Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Exception occurred: {e}"); - } - return nextPathPos.m_lane; - } + return bestNextLaneIndex; + } catch (Exception e) { + Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Exception occurred: {e}"); + } + return nextPathPos.m_lane; + } - public bool MayFindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState) { - GlobalConfig conf = GlobalConfig.Instance; + public bool MayFindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState) { + GlobalConfig conf = GlobalConfig.Instance; #if DEBUG - bool debug = false; // conf.Debug.Switches[17] && (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId); - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}) called."); - } + bool debug = false; // conf.Debug.Switches[17] && (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId); + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}) called."); + } #endif - if (!Options.advancedAI) { + if (!Options.advancedAI) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Advanced Vehicle AI is disabled."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Advanced Vehicle AI is disabled."); + } #endif - return false; - } + return false; + } - if (vehicleState.heavyVehicle) { + if (vehicleState.heavyVehicle) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Vehicle is heavy."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Vehicle is heavy."); + } #endif - return false; - } + return false; + } - if ((vehicleState.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) == ExtVehicleType.None) { + if ((vehicleState.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) == ExtVehicleType.None) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. vehicleType={vehicleState.vehicleType}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. vehicleType={vehicleState.vehicleType}"); + } #endif - return false; - } + return false; + } - uint vehicleRand = Constants.ManagerFactory.ExtVehicleManager.GetStaticVehicleRand(vehicleId); + uint vehicleRand = Constants.ManagerFactory.ExtVehicleManager.GetStaticVehicleRand(vehicleId); - if (vehicleRand < 100 - (int)Options.altLaneSelectionRatio) { + if (vehicleRand < 100 - (int)Options.altLaneSelectionRatio) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking (randomization)."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking (randomization)."); + } #endif - return false; - } + return false; + } - return true; - } - } -} + return true; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs b/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs index a45982ff5..2de95b611 100644 --- a/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs @@ -1,572 +1,582 @@ -using ColossalFramework; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Text; -using TrafficManager.Geometry; -using TrafficManager.Geometry.Impl; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using TrafficManager.Util; - -namespace TrafficManager.Manager.Impl { - public class VehicleRestrictionsManager : AbstractGeometryObservingManager, ICustomDataManager>, IVehicleRestrictionsManager { - public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail; - public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.PassengerTrain | ExtVehicleType.CargoTrain | ExtVehicleType.PassengerCar | ExtVehicleType.Bus | ExtVehicleType.Taxi | ExtVehicleType.CargoTruck | ExtVehicleType.Service | ExtVehicleType.Emergency; - public static readonly float[] PATHFIND_PENALTIES = new float[] { 10f, 100f, 1000f }; - - public static readonly VehicleRestrictionsManager Instance = new VehicleRestrictionsManager(); - - private VehicleRestrictionsManager() { - - } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"- Not implemented -"); - // TODO implement - } - - /// - /// For each segment id and lane index: Holds the default set of vehicle types allowed for the lane - /// - private ExtVehicleType?[][][] defaultVehicleTypeCache = null; - - /// - /// Determines the allowed vehicle types that may approach the given node from the given segment. - /// - /// - /// - /// - [Obsolete] - public ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { // TODO optimize method (don't depend on collections!) - ExtVehicleType ret = ExtVehicleType.None; - foreach (ExtVehicleType vehicleType in GetAllowedVehicleTypesAsSet(segmentId, nodeId, busLaneMode)) { - ret |= vehicleType; - } - return ret; - } - - /// - /// Determines the allowed vehicle types that may approach the given node from the given segment. - /// - /// - /// - /// - [Obsolete] - public HashSet GetAllowedVehicleTypesAsSet(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { - HashSet ret = new HashSet(GetAllowedVehicleTypesAsDict(segmentId, nodeId, busLaneMode).Values); - return ret; - } - - /// - /// Determines the allowed vehicle types that may approach the given node from the given segment (lane-wise). - /// - /// - /// - /// - public IDictionary GetAllowedVehicleTypesAsDict(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { - IDictionary ret = new TinyDictionary(); - - NetManager netManager = Singleton.instance; - if (segmentId == 0 || (netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None || - nodeId == 0 || (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) { - return ret; - } - - var dir = NetInfo.Direction.Forward; - var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); - - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - int numLanes = segmentInfo.m_lanes.Length; - uint laneIndex = 0; - while (laneIndex < numLanes && curLaneId != 0u) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - if (laneInfo.m_laneType == NetInfo.LaneType.Vehicle || laneInfo.m_laneType == NetInfo.LaneType.TransportVehicle) { - if ((laneInfo.m_vehicleType & VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { - ushort toNodeId = (laneInfo.m_finalDirection & dir2) != NetInfo.Direction.None ? netManager.m_segments.m_buffer[segmentId].m_endNode : netManager.m_segments.m_buffer[segmentId].m_startNode; - if ((laneInfo.m_finalDirection & NetInfo.Direction.Both) == NetInfo.Direction.Both || toNodeId == nodeId) { - ExtVehicleType vehicleTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); - ret[(byte)laneIndex] = vehicleTypes; - } - } - } - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - - return ret; - } - - /// - /// Determines the allowed vehicle types for the given segment and lane. - /// - /// - /// - /// - /// - /// - public ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { - ExtVehicleType?[] fastArray = Flags.laneAllowedVehicleTypesArray[segmentId]; - if (fastArray != null && fastArray.Length > laneIndex && fastArray[laneIndex] != null) { - return (ExtVehicleType)fastArray[laneIndex]; - } - - return GetDefaultAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); - } - - /// - /// Determines the default set of allowed vehicle types for a given segment and lane. - /// - /// - /// - /// - /// - /// - public ExtVehicleType GetDefaultAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { - // manage cached default vehicle types - if (defaultVehicleTypeCache == null) { - defaultVehicleTypeCache = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][][]; - } - - ExtVehicleType?[] cachedDefaultTypes = null; - int cacheIndex = (int)busLaneMode; - - if (defaultVehicleTypeCache[segmentId] != null) { - cachedDefaultTypes = defaultVehicleTypeCache[segmentId][cacheIndex]; - } - - if (cachedDefaultTypes == null || cachedDefaultTypes.Length != segmentInfo.m_lanes.Length) { - ExtVehicleType?[][] segmentCache = new ExtVehicleType?[3][]; - segmentCache[0] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; - segmentCache[1] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; - segmentCache[2] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; - defaultVehicleTypeCache[segmentId] = segmentCache; - cachedDefaultTypes = segmentCache[cacheIndex]; - } - - ExtVehicleType? defaultVehicleType = cachedDefaultTypes[laneIndex]; - if (defaultVehicleType == null) { - defaultVehicleType = GetDefaultAllowedVehicleTypes(laneInfo, busLaneMode); - cachedDefaultTypes[laneIndex] = defaultVehicleType; - } - return (ExtVehicleType)defaultVehicleType; - } - - public ExtVehicleType GetDefaultAllowedVehicleTypes(NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { - ExtVehicleType ret = ExtVehicleType.None; - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.Bicycle; - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.Tram; - if (busLaneMode == VehicleRestrictionsMode.Restricted || - (busLaneMode == VehicleRestrictionsMode.Configured && Options.banRegularTrafficOnBusLanes)) { - if ((laneInfo.m_laneType & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None) - ret |= ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service | ExtVehicleType.Emergency; - else if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.RoadVehicle; - } else { - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.RoadVehicle; - } - if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.RailVehicle; - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Ship) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.Ship; - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Plane) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.Plane; - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Ferry) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.Ferry; - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Blimp) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.Blimp; - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.CableCar) != VehicleInfo.VehicleType.None) - ret |= ExtVehicleType.CableCar; - return ret; - } - - /// - /// Determines the default set of allowed vehicle types for a given lane. - /// - /// - /// - /// - /// - /// - internal ExtVehicleType GetDefaultAllowedVehicleTypes(uint laneId, VehicleRestrictionsMode busLaneMode) { - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & NetLane.Flags.Created) == NetLane.Flags.None) - return ExtVehicleType.None; - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) - return ExtVehicleType.None; - - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - int numLanes = segmentInfo.m_lanes.Length; - uint laneIndex = 0; - while (laneIndex < numLanes && curLaneId != 0u) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - if (curLaneId == laneId) { - return GetDefaultAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); - } - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - - return ExtVehicleType.None; - } - - /// - /// Sets the allowed vehicle types for the given segment and lane. - /// - /// - /// - /// - /// - /// - internal bool SetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, ExtVehicleType allowedTypes) { - if (! Services.NetService.IsLaneValid(laneId)) { - return false; - } - - if (! Services.NetService.IsSegmentValid(segmentId)) { - // TODO we do not need the segmentId given here. Lane is enough - return false; - } - - allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask - Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); - NotifyStartEndNode(segmentId); - - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(segmentId); - } - - return true; - } - - /// - /// Adds the given vehicle type to the set of allowed vehicles at the specified lane - /// - /// - /// - /// - /// - /// - /// - public void AddAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType) { - if (!Services.NetService.IsLaneValid(laneId)) { - return; - } - - if (!Services.NetService.IsSegmentValid(segmentId)) { - // TODO we do not need the segmentId given here. Lane is enough - return; - } - - ExtVehicleType allowedTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - allowedTypes |= vehicleType; - allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask - Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); - NotifyStartEndNode(segmentId); - - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(segmentId); - } - } - - /// - /// Removes the given vehicle type from the set of allowed vehicles at the specified lane - /// - /// - /// - /// - /// - /// - /// - public void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType) { - if (!Services.NetService.IsLaneValid(laneId)) { - return; - } - - if (!Services.NetService.IsSegmentValid(segmentId)) { - // TODO we do not need the segmentId given here. Lane is enough - return; - } - - ExtVehicleType allowedTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - allowedTypes &= ~vehicleType; - allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask - Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); - NotifyStartEndNode(segmentId); - - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(segmentId); - } - } - - public void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add) { - if (add) - AddAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType); - else - RemoveAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType); - } - - public bool HasSegmentRestrictions(ushort segmentId) { // TODO clean up restrictions (currently we do not check if restrictions are equal with the base type) - bool ret = false; - Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { - ExtVehicleType defaultMask = GetDefaultAllowedVehicleTypes(laneInfo, VehicleRestrictionsMode.Unrestricted); - ExtVehicleType currentMask = GetAllowedVehicleTypes(segmentId, segment.Info, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - - if (defaultMask != currentMask) { - ret = true; - return false; - } - return true; - }); - - return ret; - } - - /// - /// Determines if a vehicle may use the given lane. - /// - /// - /// - /// - /// - /// - /// - public bool MayUseLane(ExtVehicleType type, ushort segmentId, byte laneIndex, NetInfo segmentInfo) { - if (type == ExtVehicleType.None /* || type == ExtVehicleType.Tram*/) - return true; - - /*if (laneInfo == null) - laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex];*/ - - if (segmentInfo == null || laneIndex >= segmentInfo.m_lanes.Length) { - return true; - } - - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) - return true; - - if (!Options.vehicleRestrictionsEnabled) { - return (GetDefaultAllowedVehicleTypes(laneInfo, VehicleRestrictionsMode.Configured) & type) != ExtVehicleType.None; - } - - return ((GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured) & type) != ExtVehicleType.None); - } - - /// - /// Determines the maximum allowed set of vehicles (the base mask) for a given lane - /// - /// - /// - public ExtVehicleType GetBaseMask(NetInfo.Lane laneInfo, VehicleRestrictionsMode includeBusLanes) { - return GetDefaultAllowedVehicleTypes(laneInfo, includeBusLanes); - } - - /// - /// Determines the maximum allowed set of vehicles (the base mask) for a given lane - /// - /// - /// - public ExtVehicleType GetBaseMask(uint laneId, VehicleRestrictionsMode includeBusLanes) { - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & NetLane.Flags.Created) == NetLane.Flags.None) - return ExtVehicleType.None; - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) - return ExtVehicleType.None; - - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - int numLanes = segmentInfo.m_lanes.Length; - uint laneIndex = 0; - while (laneIndex < numLanes && curLaneId != 0u) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - if (curLaneId == laneId) { - return GetBaseMask(laneInfo, includeBusLanes); - } - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - return ExtVehicleType.None; - } - - public bool IsAllowed(ExtVehicleType? allowedTypes, ExtVehicleType vehicleType) { - return allowedTypes == null || ((ExtVehicleType)allowedTypes & vehicleType) != ExtVehicleType.None; - } - - public bool IsBicycleAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Bicycle); - } - - public bool IsBusAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Bus); - } - - public bool IsCargoTrainAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.CargoTrain); - } - - public bool IsCargoTruckAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.CargoTruck); - } - - public bool IsEmergencyAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Emergency); - } - - public bool IsPassengerCarAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.PassengerCar); - } - - public bool IsPassengerTrainAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.PassengerTrain); - } - - public bool IsServiceAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Service); - } - - public bool IsTaxiAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Taxi); - } - - public bool IsTramAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Tram); - } - - public bool IsBlimpAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Blimp); - } - - public bool IsCableCarAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.CableCar); - } - - public bool IsFerryAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.Ferry); - } - - public bool IsRailVehicleAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.RailVehicle); - } - - public bool IsRoadVehicleAllowed(ExtVehicleType? allowedTypes) { - return IsAllowed(allowedTypes, ExtVehicleType.RoadVehicle); - } - - public bool IsRailLane(NetInfo.Lane laneInfo) { - return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Train) != VehicleInfo.VehicleType.None; - } - - public bool IsRoadLane(NetInfo.Lane laneInfo) { - return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None; - } - - public bool IsTramLane(NetInfo.Lane laneInfo) { - return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None; - } - - public bool IsRailSegment(NetInfo segmentInfo) { - ItemClass connectionClass = segmentInfo.GetConnectionClass(); - return connectionClass.m_service == ItemClass.Service.PublicTransport && connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain; - } - - public bool IsRoadSegment(NetInfo segmentInfo) { - ItemClass connectionClass = segmentInfo.GetConnectionClass(); - return connectionClass.m_service == ItemClass.Service.Road; - } - - public bool IsMonorailSegment(NetInfo segmentInfo) { - ItemClass connectionClass = segmentInfo.GetConnectionClass(); - return connectionClass.m_service == ItemClass.Service.PublicTransport && connectionClass.m_subService == ItemClass.SubService.PublicTransportMonorail; - } - - internal void ClearCache(ushort segmentId) { - if (defaultVehicleTypeCache != null) { - defaultVehicleTypeCache[segmentId] = null; - } - } - - internal void ClearCache() { - defaultVehicleTypeCache = null; - } - - public void NotifyStartEndNode(ushort segmentId) { - // TODO this is hacky. Instead of notifying geometry observers we should add a seperate notification mechanic - // notify observers of start node and end node (e.g. for separate traffic lights) - ushort startNodeId = Singleton.instance.m_segments.m_buffer[segmentId].m_startNode; - ushort endNodeId = Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; - if (startNodeId != 0) { - Constants.ManagerFactory.GeometryManager.MarkAsUpdated(startNodeId); - } - if (endNodeId != 0) { - Constants.ManagerFactory.GeometryManager.MarkAsUpdated(endNodeId); - } - } - - protected override void HandleInvalidSegment(ref ExtSegment seg) { - Flags.resetSegmentVehicleRestrictions(seg.segmentId); - ClearCache(seg.segmentId); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - ClearCache(); - } - - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading lane vehicle restriction data. {data.Count} elements"); - foreach (Configuration.LaneVehicleTypes laneVehicleTypes in data) { - try { - if (!Services.NetService.IsLaneValid(laneVehicleTypes.laneId)) - continue; - - ExtVehicleType baseMask = GetBaseMask(laneVehicleTypes.laneId, VehicleRestrictionsMode.Configured); - ExtVehicleType maskedType = laneVehicleTypes.vehicleTypes & baseMask; +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using API.Manager; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using State; + using Traffic; + using Traffic.Data; + using Util; + using ExtVehicleType = global::TrafficManager.API.Traffic.Enums.ExtVehicleType; + + public class VehicleRestrictionsManager : AbstractGeometryObservingManager, + ICustomDataManager>, + IVehicleRestrictionsManager { + public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + + public const VehicleInfo.VehicleType VEHICLE_TYPES = + VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram + | VehicleInfo.VehicleType.Monorail; + + public const ExtVehicleType EXT_VEHICLE_TYPES = + ExtVehicleType.PassengerTrain | ExtVehicleType.CargoTrain | ExtVehicleType.PassengerCar + | ExtVehicleType.Bus | ExtVehicleType.Taxi | ExtVehicleType.CargoTruck + | ExtVehicleType.Service | ExtVehicleType.Emergency; + + public static readonly float[] PATHFIND_PENALTIES = { 10f, 100f, 1000f }; + + public static readonly VehicleRestrictionsManager Instance = new VehicleRestrictionsManager(); + + private VehicleRestrictionsManager() { + + } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"- Not implemented -"); + // TODO implement + } + + /// + /// For each segment id and lane index: Holds the default set of vehicle types allowed for the lane + /// + private ExtVehicleType?[][][] defaultVehicleTypeCache = null; + + /// + /// Determines the allowed vehicle types that may approach the given node from the given segment. + /// + /// + /// + /// + [Obsolete] + public ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { // TODO optimize method (don't depend on collections!) + ExtVehicleType ret = ExtVehicleType.None; + foreach (ExtVehicleType vehicleType in GetAllowedVehicleTypesAsSet(segmentId, nodeId, busLaneMode)) { + ret |= vehicleType; + } + return ret; + } + + /// + /// Determines the allowed vehicle types that may approach the given node from the given segment. + /// + /// + /// + /// + [Obsolete] + public HashSet GetAllowedVehicleTypesAsSet(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { + HashSet ret = new HashSet(GetAllowedVehicleTypesAsDict(segmentId, nodeId, busLaneMode).Values); + return ret; + } + + /// + /// Determines the allowed vehicle types that may approach the given node from the given segment (lane-wise). + /// + /// + /// + /// + public IDictionary GetAllowedVehicleTypesAsDict(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { + IDictionary ret = new TinyDictionary(); + + NetManager netManager = Singleton.instance; + if (segmentId == 0 || (netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None || + nodeId == 0 || (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) { + return ret; + } + + var dir = NetInfo.Direction.Forward; + var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); + + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + int numLanes = segmentInfo.m_lanes.Length; + uint laneIndex = 0; + while (laneIndex < numLanes && curLaneId != 0u) { + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + if (laneInfo.m_laneType == NetInfo.LaneType.Vehicle || laneInfo.m_laneType == NetInfo.LaneType.TransportVehicle) { + if ((laneInfo.m_vehicleType & VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { + ushort toNodeId = (laneInfo.m_finalDirection & dir2) != NetInfo.Direction.None ? netManager.m_segments.m_buffer[segmentId].m_endNode : netManager.m_segments.m_buffer[segmentId].m_startNode; + if ((laneInfo.m_finalDirection & NetInfo.Direction.Both) == NetInfo.Direction.Both || toNodeId == nodeId) { + ExtVehicleType vehicleTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); + ret[(byte)laneIndex] = vehicleTypes; + } + } + } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + + return ret; + } + + /// + /// Determines the allowed vehicle types for the given segment and lane. + /// + /// + /// + /// + /// + /// + public ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { + var fastArray = Flags.laneAllowedVehicleTypesArray[segmentId]; + if (fastArray != null && fastArray.Length > laneIndex && fastArray[laneIndex] != null) { + return (ExtVehicleType)fastArray[laneIndex]; + } + + return GetDefaultAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); + } + + /// + /// Determines the default set of allowed vehicle types for a given segment and lane. + /// + /// + /// + /// + /// + /// + public ExtVehicleType GetDefaultAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { + // manage cached default vehicle types + if (defaultVehicleTypeCache == null) { + defaultVehicleTypeCache = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][][]; + } + + ExtVehicleType?[] cachedDefaultTypes = null; + int cacheIndex = (int)busLaneMode; + + if (defaultVehicleTypeCache[segmentId] != null) { + cachedDefaultTypes = defaultVehicleTypeCache[segmentId][cacheIndex]; + } + + if (cachedDefaultTypes == null || cachedDefaultTypes.Length != segmentInfo.m_lanes.Length) { + ExtVehicleType?[][] segmentCache = new ExtVehicleType?[3][]; + segmentCache[0] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; + segmentCache[1] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; + segmentCache[2] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; + defaultVehicleTypeCache[segmentId] = segmentCache; + cachedDefaultTypes = segmentCache[cacheIndex]; + } + + ExtVehicleType? defaultVehicleType = cachedDefaultTypes[laneIndex]; + if (defaultVehicleType == null) { + defaultVehicleType = GetDefaultAllowedVehicleTypes(laneInfo, busLaneMode); + cachedDefaultTypes[laneIndex] = defaultVehicleType; + } + return (ExtVehicleType)defaultVehicleType; + } + + public ExtVehicleType GetDefaultAllowedVehicleTypes(NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { + ExtVehicleType ret = ExtVehicleType.None; + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.Bicycle; + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.Tram; + if (busLaneMode == VehicleRestrictionsMode.Restricted || + (busLaneMode == VehicleRestrictionsMode.Configured && Options.banRegularTrafficOnBusLanes)) { + if ((laneInfo.m_laneType & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None) + ret |= ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service | ExtVehicleType.Emergency; + else if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.RoadVehicle; + } else { + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.RoadVehicle; + } + if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.RailVehicle; + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Ship) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.Ship; + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Plane) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.Plane; + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Ferry) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.Ferry; + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Blimp) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.Blimp; + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.CableCar) != VehicleInfo.VehicleType.None) + ret |= ExtVehicleType.CableCar; + return ret; + } + + /// + /// Determines the default set of allowed vehicle types for a given lane. + /// + /// + /// + /// + /// + /// + internal ExtVehicleType GetDefaultAllowedVehicleTypes(uint laneId, VehicleRestrictionsMode busLaneMode) { + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & NetLane.Flags.Created) == NetLane.Flags.None) + return ExtVehicleType.None; + ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) + return ExtVehicleType.None; + + NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + int numLanes = segmentInfo.m_lanes.Length; + uint laneIndex = 0; + while (laneIndex < numLanes && curLaneId != 0u) { + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + if (curLaneId == laneId) { + return GetDefaultAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); + } + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + + return ExtVehicleType.None; + } + + /// + /// Sets the allowed vehicle types for the given segment and lane. + /// + /// + /// + /// + /// + /// + internal bool SetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, ExtVehicleType allowedTypes) { + if (! Services.NetService.IsLaneValid(laneId)) { + return false; + } + + if (! Services.NetService.IsSegmentValid(segmentId)) { + // TODO we do not need the segmentId given here. Lane is enough + return false; + } + + allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask + Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); + NotifyStartEndNode(segmentId); + + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(segmentId); + } + + return true; + } + + /// + /// Adds the given vehicle type to the set of allowed vehicles at the specified lane + /// + /// + /// + /// + /// + /// + /// + public void AddAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType) { + if (!Services.NetService.IsLaneValid(laneId)) { + return; + } + + if (!Services.NetService.IsSegmentValid(segmentId)) { + // TODO we do not need the segmentId given here. Lane is enough + return; + } + + ExtVehicleType allowedTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); + allowedTypes |= vehicleType; + allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask + Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); + NotifyStartEndNode(segmentId); + + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(segmentId); + } + } + + /// + /// Removes the given vehicle type from the set of allowed vehicles at the specified lane + /// + /// + /// + /// + /// + /// + /// + public void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType) { + if (!Services.NetService.IsLaneValid(laneId)) { + return; + } + + if (!Services.NetService.IsSegmentValid(segmentId)) { + // TODO we do not need the segmentId given here. Lane is enough + return; + } + + ExtVehicleType allowedTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); + allowedTypes &= ~vehicleType; + allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask + Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); + NotifyStartEndNode(segmentId); + + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(segmentId); + } + } + + public void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add) { + if (add) + AddAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType); + else + RemoveAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType); + } + + public bool HasSegmentRestrictions(ushort segmentId) { // TODO clean up restrictions (currently we do not check if restrictions are equal with the base type) + bool ret = false; + Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { + ExtVehicleType defaultMask = GetDefaultAllowedVehicleTypes(laneInfo, VehicleRestrictionsMode.Unrestricted); + ExtVehicleType currentMask = GetAllowedVehicleTypes(segmentId, segment.Info, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); + + if (defaultMask != currentMask) { + ret = true; + return false; + } + return true; + }); + + return ret; + } + + /// + /// Determines if a vehicle may use the given lane. + /// + /// + /// + /// + /// + /// + /// + public bool MayUseLane(ExtVehicleType type, ushort segmentId, byte laneIndex, NetInfo segmentInfo) { + if (type == ExtVehicleType.None /* || type == ExtVehicleType.Tram*/) + return true; + + /*if (laneInfo == null) + laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex];*/ + + if (segmentInfo == null || laneIndex >= segmentInfo.m_lanes.Length) { + return true; + } + + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) + return true; + + if (!Options.vehicleRestrictionsEnabled) { + return (GetDefaultAllowedVehicleTypes(laneInfo, VehicleRestrictionsMode.Configured) & type) != ExtVehicleType.None; + } + + return ((GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured) & type) != ExtVehicleType.None); + } + + /// + /// Determines the maximum allowed set of vehicles (the base mask) for a given lane + /// + /// + /// + public ExtVehicleType GetBaseMask(NetInfo.Lane laneInfo, VehicleRestrictionsMode includeBusLanes) { + return GetDefaultAllowedVehicleTypes(laneInfo, includeBusLanes); + } + + /// + /// Determines the maximum allowed set of vehicles (the base mask) for a given lane + /// + /// + /// + public ExtVehicleType GetBaseMask(uint laneId, VehicleRestrictionsMode includeBusLanes) { + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & NetLane.Flags.Created) == NetLane.Flags.None) + return ExtVehicleType.None; + ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) + return ExtVehicleType.None; + + NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + int numLanes = segmentInfo.m_lanes.Length; + uint laneIndex = 0; + while (laneIndex < numLanes && curLaneId != 0u) { + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + if (curLaneId == laneId) { + return GetBaseMask(laneInfo, includeBusLanes); + } + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + return ExtVehicleType.None; + } + + public bool IsAllowed(ExtVehicleType? allowedTypes, ExtVehicleType vehicleType) { + return allowedTypes == null || ((ExtVehicleType)allowedTypes & vehicleType) != ExtVehicleType.None; + } + + public bool IsBicycleAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Bicycle); + } + + public bool IsBusAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Bus); + } + + public bool IsCargoTrainAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.CargoTrain); + } + + public bool IsCargoTruckAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.CargoTruck); + } + + public bool IsEmergencyAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Emergency); + } + + public bool IsPassengerCarAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.PassengerCar); + } + + public bool IsPassengerTrainAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.PassengerTrain); + } + + public bool IsServiceAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Service); + } + + public bool IsTaxiAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Taxi); + } + + public bool IsTramAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Tram); + } + + public bool IsBlimpAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Blimp); + } + + public bool IsCableCarAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.CableCar); + } + + public bool IsFerryAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.Ferry); + } + + public bool IsRailVehicleAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.RailVehicle); + } + + public bool IsRoadVehicleAllowed(ExtVehicleType? allowedTypes) { + return IsAllowed(allowedTypes, ExtVehicleType.RoadVehicle); + } + + public bool IsRailLane(NetInfo.Lane laneInfo) { + return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Train) != VehicleInfo.VehicleType.None; + } + + public bool IsRoadLane(NetInfo.Lane laneInfo) { + return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None; + } + + public bool IsTramLane(NetInfo.Lane laneInfo) { + return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None; + } + + public bool IsRailSegment(NetInfo segmentInfo) { + ItemClass connectionClass = segmentInfo.GetConnectionClass(); + return connectionClass.m_service == ItemClass.Service.PublicTransport && connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain; + } + + public bool IsRoadSegment(NetInfo segmentInfo) { + ItemClass connectionClass = segmentInfo.GetConnectionClass(); + return connectionClass.m_service == ItemClass.Service.Road; + } + + public bool IsMonorailSegment(NetInfo segmentInfo) { + ItemClass connectionClass = segmentInfo.GetConnectionClass(); + return connectionClass.m_service == ItemClass.Service.PublicTransport && connectionClass.m_subService == ItemClass.SubService.PublicTransportMonorail; + } + + internal void ClearCache(ushort segmentId) { + if (defaultVehicleTypeCache != null) { + defaultVehicleTypeCache[segmentId] = null; + } + } + + internal void ClearCache() { + defaultVehicleTypeCache = null; + } + + public void NotifyStartEndNode(ushort segmentId) { + // TODO this is hacky. Instead of notifying geometry observers we should add a seperate notification mechanic + // notify observers of start node and end node (e.g. for separate traffic lights) + ushort startNodeId = Singleton.instance.m_segments.m_buffer[segmentId].m_startNode; + ushort endNodeId = Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; + if (startNodeId != 0) { + Constants.ManagerFactory.GeometryManager.MarkAsUpdated(startNodeId); + } + if (endNodeId != 0) { + Constants.ManagerFactory.GeometryManager.MarkAsUpdated(endNodeId); + } + } + + protected override void HandleInvalidSegment(ref ExtSegment seg) { + Flags.resetSegmentVehicleRestrictions(seg.segmentId); + ClearCache(seg.segmentId); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + ClearCache(); + } + + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading lane vehicle restriction data. {data.Count} elements"); + foreach (Configuration.LaneVehicleTypes laneVehicleTypes in data) { + try { + if (!Services.NetService.IsLaneValid(laneVehicleTypes.laneId)) + continue; + + var baseMask = GetBaseMask(laneVehicleTypes.laneId, + VehicleRestrictionsMode.Configured); + var maskedType = laneVehicleTypes.ApiVehicleTypes & baseMask; #if DEBUGLOAD Log._Debug($"Loading lane vehicle restriction: lane {laneVehicleTypes.laneId} = {laneVehicleTypes.vehicleTypes}, masked = {maskedType}"); #endif - if (maskedType != baseMask) { - Flags.setLaneAllowedVehicleTypes(laneVehicleTypes.laneId, maskedType); - } else { + if (maskedType != baseMask) { + Flags.setLaneAllowedVehicleTypes(laneVehicleTypes.laneId, maskedType); + } else { #if DEBUGLOAD Log._Debug($"Masked type does not differ from base type. Ignoring."); #endif - } - } catch (Exception e) { - // ignore, as it's probably corrupt save data. it'll be culled on next save - Log.Warning("Error loading data from vehicle restrictions: " + e.ToString()); - success = false; - } - } - return success; - } - - public List SaveData(ref bool success) { - List ret = new List(); - foreach (KeyValuePair e in Flags.getAllLaneAllowedVehicleTypes()) { - try { - ret.Add(new Configuration.LaneVehicleTypes(e.Key, e.Value)); - Log._Trace($"Saving lane vehicle restriction: laneid={e.Key} vehicleType={e.Value}"); - } catch (Exception ex) { - Log.Error($"Exception occurred while saving lane vehicle restrictions @ {e.Key}: {ex.ToString()}"); - success = false; - } - } - return ret; - } - } -} + } + } catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Warning("Error loading data from vehicle restrictions: " + e.ToString()); + success = false; + } + } + return success; + } + + public List SaveData(ref bool success) { + List ret = new List(); + foreach (KeyValuePair e in Flags.getAllLaneAllowedVehicleTypes()) { + try { + ret.Add(new Configuration.LaneVehicleTypes(e.Key, LegacyExtVehicleType.ToOld(e.Value))); + Log._Trace($"Saving lane vehicle restriction: laneid={e.Key} vehicleType={e.Value}"); + } catch (Exception ex) { + Log.Error($"Exception occurred while saving lane vehicle restrictions @ {e.Key}: {ex.ToString()}"); + success = false; + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Properties/AssemblyInfo.cs b/TLM/TLM/Properties/AssemblyInfo.cs index 5b22dc1bc..98ef1c155 100644 --- a/TLM/TLM/Properties/AssemblyInfo.cs +++ b/TLM/TLM/Properties/AssemblyInfo.cs @@ -35,4 +35,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.*")] -[assembly:TypeForwardedTo(typeof(ExtVehicleType))] \ No newline at end of file +// [assembly:TypeForwardedTo(typeof(ExtVehicleType))] \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/Debug.cs b/TLM/TLM/State/ConfigData/Debug.cs index da63f10fc..9460b0d66 100644 --- a/TLM/TLM/State/ConfigData/Debug.cs +++ b/TLM/TLM/State/ConfigData/Debug.cs @@ -1,54 +1,62 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using static TrafficManager.Traffic.Data.ExtCitizenInstance; +namespace TrafficManager.State.ConfigData { + using System; + using Traffic; + using Traffic.Enums; -namespace TrafficManager.State.ConfigData { #if DEBUG - public class Debug { - public bool[] Switches = { - false, // 0: path-finding debug log - false, // 1: routing basic debug log - false, // 2: parking ai debug log (basic) - false, // 3: do not actually repair stuck vehicles/cims, just report - false, // 4: parking ai debug log (extended) - false, // 5: geometry debug log - false, // 6: debug parking AI distance issue - false, // 7: debug TTL - false, // 8: debug routing - false, // 9: debug vehicle to segment end linking - false, // 10: prevent routing recalculation on global configuration reload - false, // 11: debug junction restrictions - false, // 12: - unused - - false, // 13: priority rules debug - false, // 14: disable GUI overlay of citizens having a valid path - false, // 15: disable checking of other vehicles for trams - false, // 16: debug TramBaseAI.SimulationStep (2) - false, // 17: debug alternative lane selection - false, // 18: transport line path-find debugging - false, // 19: enable obligation to drive on the right hand side of the road - false, // 20: debug realistic public transport - false, // 21: debug "CalculateSegmentPosition" - false, // 22: parking ai debug log (vehicles) - false, // 23: debug lane connections - false, // 24: debug resource loading - false // 25: debug turn-on-red - }; + public class Debug { + public bool[] Switches = { + false, // 0: path-finding debug log + false, // 1: routing basic debug log + false, // 2: parking ai debug log (basic) + false, // 3: do not actually repair stuck vehicles/cims, just report + false, // 4: parking ai debug log (extended) + false, // 5: geometry debug log + false, // 6: debug parking AI distance issue + false, // 7: debug TTL + false, // 8: debug routing + false, // 9: debug vehicle to segment end linking + false, // 10: prevent routing recalculation on global configuration reload + false, // 11: debug junction restrictions + false, // 12: - unused - + false, // 13: priority rules debug + false, // 14: disable GUI overlay of citizens having a valid path + false, // 15: disable checking of other vehicles for trams + false, // 16: debug TramBaseAI.SimulationStep (2) + false, // 17: debug alternative lane selection + false, // 18: transport line path-find debugging + false, // 19: enable obligation to drive on the right hand side of the road + false, // 20: debug realistic public transport + false, // 21: debug "CalculateSegmentPosition" + false, // 22: parking ai debug log (vehicles) + false, // 23: debug lane connections + false, // 24: debug resource loading + false // 25: debug turn-on-red + }; - public int NodeId = 0; - public int SegmentId = 0; - public int StartSegmentId = 0; - public int EndSegmentId = 0; - public int VehicleId = 0; - public int CitizenInstanceId = 0; - public uint CitizenId = 0; - public uint SourceBuildingId = 0; - public uint TargetBuildingId = 0; - public ExtVehicleType ExtVehicleType = ExtVehicleType.None; - public ExtPathMode ExtPathMode = ExtPathMode.None; - } + public int NodeId = 0; + public int SegmentId = 0; + public int StartSegmentId = 0; + public int EndSegmentId = 0; + public int VehicleId = 0; + public int CitizenInstanceId = 0; + public uint CitizenId = 0; + public uint SourceBuildingId = 0; + public uint TargetBuildingId = 0; + + [Obsolete] + public ExtVehicleType ExtVehicleType = ExtVehicleType.None; + + /// + /// This adds access to the new moved type from the compatible old field + /// + // Property will not be serialized, permit use of obsolete symbol +#pragma warning disable 612 + public API.Traffic.Enums.ExtVehicleType ApiExtVehicleType + => LegacyExtVehicleType.ToNew(ExtVehicleType); +#pragma warning restore 612 + + public ExtPathMode ExtPathMode = ExtPathMode.None; + } #endif -} +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/TimedTrafficLights.cs b/TLM/TLM/State/ConfigData/TimedTrafficLights.cs index 67238fcad..4a9b2c25b 100644 --- a/TLM/TLM/State/ConfigData/TimedTrafficLights.cs +++ b/TLM/TLM/State/ConfigData/TimedTrafficLights.cs @@ -5,6 +5,8 @@ using TrafficManager.TrafficLight; namespace TrafficManager.State.ConfigData { + using API.Traffic.Enums; + public class TimedTrafficLights { /// /// TTL wait/flow calculation mode diff --git a/TLM/TLM/State/Configuration.cs b/TLM/TLM/State/Configuration.cs index ed93a6a1d..30100795f 100644 --- a/TLM/TLM/State/Configuration.cs +++ b/TLM/TLM/State/Configuration.cs @@ -1,295 +1,314 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization; -using System.Xml.Serialization; -using TrafficManager.State; -using TrafficManager.Traffic.Data; - // TODO this class should be moved to TrafficManager.State, but the deserialization fails if we just do that now. Anyway, we should get rid of these crazy lists of arrays. So let's move the class when we decide rework the load/save system. namespace TrafficManager { - [Serializable] - public class Configuration { - [Serializable] - public class LaneSpeedLimit { - public uint laneId; - public ushort speedLimit; - - public LaneSpeedLimit(uint laneId, float speedLimit) { - this.laneId = laneId; - this.speedLimit = (ushort)(speedLimit * SpeedLimit.SPEED_TO_KMPH); - } - } - - [Serializable] - public class LaneVehicleTypes { - public uint laneId; - public Traffic.ExtVehicleType vehicleTypes; - - public LaneVehicleTypes(uint laneId, Traffic.ExtVehicleType vehicleTypes) { - this.laneId = laneId; - this.vehicleTypes = vehicleTypes; - } - } - - [Serializable] - public class TimedTrafficLights { - public ushort nodeId; - public List nodeGroup; - public bool started; - public int currentStep; - public List timedSteps; - } - - [Serializable] - public class TimedTrafficLightsStep { - public int minTime; - public int maxTime; - public int changeMetric; - public float waitFlowBalance; - public Dictionary segmentLights; - } - - [Serializable] - public class CustomSegmentLights { - public ushort nodeId; - public ushort segmentId; - public Dictionary customLights; - public RoadBaseAI.TrafficLightState? pedestrianLightState; - public bool manualPedestrianMode; - } - - [Serializable] - public class CustomSegmentLight { - public ushort nodeId; - public ushort segmentId; - public int currentMode; - public RoadBaseAI.TrafficLightState leftLight; - public RoadBaseAI.TrafficLightState mainLight; - public RoadBaseAI.TrafficLightState rightLight; - } - - [Serializable] - public class SegmentNodeConf { - public ushort segmentId; - public SegmentNodeFlags startNodeFlags = null; - public SegmentNodeFlags endNodeFlags = null; - - public SegmentNodeConf(ushort segmentId) { - this.segmentId = segmentId; - } - } - - [Serializable] - public class ParkingRestriction { - public ushort segmentId; - public bool forwardParkingAllowed; - public bool backwardParkingAllowed; - - public ParkingRestriction(ushort segmentId) { - this.segmentId = segmentId; - forwardParkingAllowed = true; - backwardParkingAllowed = true; - } - } - - [Serializable] - public class SegmentNodeFlags { - public bool? uturnAllowed = null; + using System; + using System.Collections.Generic; + using State; + using Traffic; + using Traffic.Data; + + [Serializable] + public class Configuration { + [Serializable] + public class LaneSpeedLimit { + public uint laneId; + public ushort speedLimit; + + public LaneSpeedLimit(uint laneId, float speedLimit) { + this.laneId = laneId; + this.speedLimit = (ushort)(speedLimit * SpeedLimit.SPEED_TO_KMPH); + } + } + + [Serializable] + public class LaneVehicleTypes { + public uint laneId; + + /// + /// Do not use this, for save compatibility only. + /// + [Obsolete] + public Traffic.ExtVehicleType vehicleTypes; + + /// + /// Use this to access new ExtVehicleType, from TMPE.API + /// + // Property will not be serialized, permit use of obsolete symbol +#pragma warning disable 612 + public API.Traffic.Enums.ExtVehicleType ApiVehicleTypes + => LegacyExtVehicleType.ToNew(vehicleTypes); +#pragma warning restore 612 + + public LaneVehicleTypes(uint laneId, Traffic.ExtVehicleType vehicleTypes) { + this.laneId = laneId; + this.vehicleTypes = vehicleTypes; + } + } + + [Serializable] + public class TimedTrafficLights { + public ushort nodeId; + public List nodeGroup; + public bool started; + public int currentStep; + public List timedSteps; + } + + [Serializable] + public class TimedTrafficLightsStep { + public int minTime; + public int maxTime; + public int changeMetric; + public float waitFlowBalance; + public Dictionary segmentLights; + } + + [Serializable] + public class CustomSegmentLights { + public ushort nodeId; + public ushort segmentId; + + /// + /// This is using old type for save compatibility. + /// Use LegacyExtVehicleType helper class to convert between old/new + /// + [Obsolete] + public Dictionary customLights; + + public RoadBaseAI.TrafficLightState? pedestrianLightState; + public bool manualPedestrianMode; + } + + [Serializable] + public class CustomSegmentLight { + public ushort nodeId; + public ushort segmentId; + public int currentMode; + public RoadBaseAI.TrafficLightState leftLight; + public RoadBaseAI.TrafficLightState mainLight; + public RoadBaseAI.TrafficLightState rightLight; + } + + [Serializable] + public class SegmentNodeConf { + public ushort segmentId; + public SegmentNodeFlags startNodeFlags = null; + public SegmentNodeFlags endNodeFlags = null; + + public SegmentNodeConf(ushort segmentId) { + this.segmentId = segmentId; + } + } + + [Serializable] + public class ParkingRestriction { + public ushort segmentId; + public bool forwardParkingAllowed; + public bool backwardParkingAllowed; + + public ParkingRestriction(ushort segmentId) { + this.segmentId = segmentId; + forwardParkingAllowed = true; + backwardParkingAllowed = true; + } + } + + [Serializable] + public class SegmentNodeFlags { + public bool? uturnAllowed = null; public bool? turnOnRedAllowed = null; // controls near turns // TODO fix naming when the serialization system is updated - public bool? farTurnOnRedAllowed = null; - public bool? straightLaneChangingAllowed = null; - public bool? enterWhenBlockedAllowed = null; - public bool? pedestrianCrossingAllowed = null; - - public bool IsDefault() { - // TODO v1.11.0: check this - bool uturnIsDefault = uturnAllowed == null || (bool)uturnAllowed == Options.allowUTurns; + public bool? farTurnOnRedAllowed = null; + public bool? straightLaneChangingAllowed = null; + public bool? enterWhenBlockedAllowed = null; + public bool? pedestrianCrossingAllowed = null; + + public bool IsDefault() { + // TODO v1.11.0: check this + bool uturnIsDefault = uturnAllowed == null || (bool)uturnAllowed == Options.allowUTurns; bool turnOnRedIsDefault = turnOnRedAllowed == null || (bool)turnOnRedAllowed; - bool farTurnOnRedIsDefault = farTurnOnRedAllowed == null || (bool)farTurnOnRedAllowed; - bool straightChangeIsDefault = straightLaneChangingAllowed == null || (bool)straightLaneChangingAllowed == Options.allowLaneChangesWhileGoingStraight; - bool enterWhenBlockedIsDefault = enterWhenBlockedAllowed == null || (bool)enterWhenBlockedAllowed == Options.allowEnterBlockedJunctions; - bool pedCrossingIsDefault = pedestrianCrossingAllowed == null || (bool)pedestrianCrossingAllowed; - - return uturnIsDefault && turnOnRedIsDefault && farTurnOnRedIsDefault && straightChangeIsDefault && enterWhenBlockedIsDefault && pedCrossingIsDefault; - } - - public override string ToString() { - return $"uturnAllowed={uturnAllowed}, turnOnRedAllowed={turnOnRedAllowed}, farTurnOnRedAllowed={farTurnOnRedAllowed}, straightLaneChangingAllowed={straightLaneChangingAllowed}, enterWhenBlockedAllowed={enterWhenBlockedAllowed}, pedestrianCrossingAllowed={pedestrianCrossingAllowed}"; - } - } - - [Serializable] - public class LaneConnection { - public uint lowerLaneId; - public uint higherLaneId; - public bool lowerStartNode; - - public LaneConnection(uint lowerLaneId, uint higherLaneId, bool lowerStartNode) { - if (lowerLaneId >= higherLaneId) - throw new ArgumentException(); - this.lowerLaneId = lowerLaneId; - this.higherLaneId = higherLaneId; - this.lowerStartNode = lowerStartNode; - } - } - - [Serializable] - public class LaneArrowData { - public uint laneId; - public uint arrows; - - public LaneArrowData(uint laneId, uint arrows) { - this.laneId = laneId; - this.arrows = arrows; - } - } - - [Serializable] - public class PrioritySegment { - public ushort segmentId; - public ushort nodeId; - public int priorityType; - - public PrioritySegment(ushort segmentId, ushort nodeId, int priorityType) { - this.segmentId = segmentId; - this.nodeId = nodeId; - this.priorityType = priorityType; - } - } - - [Serializable] - public class NodeTrafficLight { - public ushort nodeId; - public bool trafficLight; - - public NodeTrafficLight(ushort nodeId, bool trafficLight) { - this.nodeId = nodeId; - this.trafficLight = trafficLight; - } - } - - [Serializable] - public class ExtCitizenInstanceData { - public uint instanceId; - public int pathMode; - public int failedParkingAttempts; - public ushort parkingSpaceLocationId; - public int parkingSpaceLocation; - public ushort parkingPathStartPositionSegment; - public byte parkingPathStartPositionLane; - public byte parkingPathStartPositionOffset; - public uint returnPathId; - public int returnPathState; - public float lastDistanceToParkedCar; - - public ExtCitizenInstanceData(uint instanceId) { - this.instanceId = instanceId; - pathMode = 0; - failedParkingAttempts = 0; - parkingSpaceLocationId = 0; - parkingSpaceLocation = 0; - parkingPathStartPositionSegment = 0; - parkingPathStartPositionLane = 0; - parkingPathStartPositionOffset = 0; - returnPathId = 0; - returnPathState = 0; - lastDistanceToParkedCar = 0; - } - } - - [Serializable] - public class ExtCitizenData { - public uint citizenId; - public int lastTransportMode; - - public ExtCitizenData(uint citizenId) { - this.citizenId = citizenId; - lastTransportMode = 0; - } - } - - /// - /// Stored ext. citizen data - /// - public List ExtCitizens = new List(); - - /// - /// Stored ext. citizen instance data - /// - public List ExtCitizenInstances = new List(); - - /// - /// Stored toggled traffic lights - /// - public List ToggledTrafficLights = new List(); - - /// - /// Stored lane connections - /// - public List LaneConnections = new List(); - - /// - /// Stored lane arrows - /// - public List LaneArrows = new List(); - - /// - /// Stored lane speed limits - /// - public List LaneSpeedLimits = new List(); - - /// - /// Stored vehicle restrictions - /// - public List LaneAllowedVehicleTypes = new List(); - - /// - /// Timed traffic lights - /// - public List TimedLights = new List(); - - /// - /// Segment-at-Node configurations - /// - public List SegmentNodeConfs = new List(); - - /// - /// Custom default speed limits (in game speed units) - /// - public Dictionary CustomDefaultSpeedLimits = new Dictionary(); - - /// - /// Priority segments - /// - public List CustomPrioritySegments = new List(); - - /// - /// Parking restrictions - /// - public List ParkingRestrictions = new List(); - - [Obsolete] - public string NodeTrafficLights = ""; - [Obsolete] - public string NodeCrosswalk = ""; - [Obsolete] - public string LaneFlags = ""; - - [Obsolete] - public List PrioritySegments = new List(); - [Obsolete] - public List NodeDictionary = new List(); - [Obsolete] - public List ManualSegments = new List(); - - [Obsolete] - public List TimedNodes = new List(); - [Obsolete] - public List TimedNodeGroups = new List(); - [Obsolete] - public List TimedNodeSteps = new List(); - [Obsolete] - public List TimedNodeStepSegments = new List(); - } -} + bool farTurnOnRedIsDefault = farTurnOnRedAllowed == null || (bool)farTurnOnRedAllowed; + bool straightChangeIsDefault = straightLaneChangingAllowed == null || (bool)straightLaneChangingAllowed == Options.allowLaneChangesWhileGoingStraight; + bool enterWhenBlockedIsDefault = enterWhenBlockedAllowed == null || (bool)enterWhenBlockedAllowed == Options.allowEnterBlockedJunctions; + bool pedCrossingIsDefault = pedestrianCrossingAllowed == null || (bool)pedestrianCrossingAllowed; + + return uturnIsDefault && turnOnRedIsDefault && farTurnOnRedIsDefault && straightChangeIsDefault && enterWhenBlockedIsDefault && pedCrossingIsDefault; + } + + public override string ToString() { + return $"uturnAllowed={uturnAllowed}, turnOnRedAllowed={turnOnRedAllowed}, farTurnOnRedAllowed={farTurnOnRedAllowed}, straightLaneChangingAllowed={straightLaneChangingAllowed}, enterWhenBlockedAllowed={enterWhenBlockedAllowed}, pedestrianCrossingAllowed={pedestrianCrossingAllowed}"; + } + } + + [Serializable] + public class LaneConnection { + public uint lowerLaneId; + public uint higherLaneId; + public bool lowerStartNode; + + public LaneConnection(uint lowerLaneId, uint higherLaneId, bool lowerStartNode) { + if (lowerLaneId >= higherLaneId) + throw new ArgumentException(); + this.lowerLaneId = lowerLaneId; + this.higherLaneId = higherLaneId; + this.lowerStartNode = lowerStartNode; + } + } + + [Serializable] + public class LaneArrowData { + public uint laneId; + public uint arrows; + + public LaneArrowData(uint laneId, uint arrows) { + this.laneId = laneId; + this.arrows = arrows; + } + } + + [Serializable] + public class PrioritySegment { + public ushort segmentId; + public ushort nodeId; + public int priorityType; + + public PrioritySegment(ushort segmentId, ushort nodeId, int priorityType) { + this.segmentId = segmentId; + this.nodeId = nodeId; + this.priorityType = priorityType; + } + } + + [Serializable] + public class NodeTrafficLight { + public ushort nodeId; + public bool trafficLight; + + public NodeTrafficLight(ushort nodeId, bool trafficLight) { + this.nodeId = nodeId; + this.trafficLight = trafficLight; + } + } + + [Serializable] + public class ExtCitizenInstanceData { + public uint instanceId; + public int pathMode; + public int failedParkingAttempts; + public ushort parkingSpaceLocationId; + public int parkingSpaceLocation; + public ushort parkingPathStartPositionSegment; + public byte parkingPathStartPositionLane; + public byte parkingPathStartPositionOffset; + public uint returnPathId; + public int returnPathState; + public float lastDistanceToParkedCar; + + public ExtCitizenInstanceData(uint instanceId) { + this.instanceId = instanceId; + pathMode = 0; + failedParkingAttempts = 0; + parkingSpaceLocationId = 0; + parkingSpaceLocation = 0; + parkingPathStartPositionSegment = 0; + parkingPathStartPositionLane = 0; + parkingPathStartPositionOffset = 0; + returnPathId = 0; + returnPathState = 0; + lastDistanceToParkedCar = 0; + } + } + + [Serializable] + public class ExtCitizenData { + public uint citizenId; + public int lastTransportMode; + + public ExtCitizenData(uint citizenId) { + this.citizenId = citizenId; + lastTransportMode = 0; + } + } + + /// + /// Stored ext. citizen data + /// + public List ExtCitizens = new List(); + + /// + /// Stored ext. citizen instance data + /// + public List ExtCitizenInstances = new List(); + + /// + /// Stored toggled traffic lights + /// + public List ToggledTrafficLights = new List(); + + /// + /// Stored lane connections + /// + public List LaneConnections = new List(); + + /// + /// Stored lane arrows + /// + public List LaneArrows = new List(); + + /// + /// Stored lane speed limits + /// + public List LaneSpeedLimits = new List(); + + /// + /// Stored vehicle restrictions + /// + public List LaneAllowedVehicleTypes = new List(); + + /// + /// Timed traffic lights + /// + public List TimedLights = new List(); + + /// + /// Segment-at-Node configurations + /// + public List SegmentNodeConfs = new List(); + + /// + /// Custom default speed limits (in game speed units) + /// + public Dictionary CustomDefaultSpeedLimits = new Dictionary(); + + /// + /// Priority segments + /// + public List CustomPrioritySegments = new List(); + + /// + /// Parking restrictions + /// + public List ParkingRestrictions = new List(); + + [Obsolete] + public string NodeTrafficLights = ""; + [Obsolete] + public string NodeCrosswalk = ""; + [Obsolete] + public string LaneFlags = ""; + + [Obsolete] + public List PrioritySegments = new List(); + [Obsolete] + public List NodeDictionary = new List(); + [Obsolete] + public List ManualSegments = new List(); + + [Obsolete] + public List TimedNodes = new List(); + [Obsolete] + public List TimedNodeGroups = new List(); + [Obsolete] + public List TimedNodeSteps = new List(); + [Obsolete] + public List TimedNodeStepSegments = new List(); + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Flags.cs b/TLM/TLM/State/Flags.cs index a160aa634..2811a2595 100644 --- a/TLM/TLM/State/Flags.cs +++ b/TLM/TLM/State/Flags.cs @@ -1,981 +1,977 @@ #define DEBUGFLAGSx -using ColossalFramework; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using TrafficManager.Util; - namespace TrafficManager.State { - [Obsolete] - public class Flags { - public static readonly uint lfr = (uint)NetLane.Flags.LeftForwardRight; - - /// - /// For each lane: Defines the lane arrows which are set - /// - private static LaneArrows?[] laneArrowFlags = null; - - /// - /// For each lane (by id): list of lanes that are connected with this lane by the T++ lane connector - /// key 1: source lane id - /// key 2: at start node? - /// values: target lane id - /// - internal static uint[][][] laneConnections = null; - - /// - /// For each lane: Defines the currently set speed limit - /// - private static Dictionary laneSpeedLimit = null; // TODO remove - - internal static float?[][] laneSpeedLimitArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index - - /// - /// For each lane: Defines the lane arrows which are set in highway rule mode (they are not saved) - /// - private static LaneArrows?[] highwayLaneArrowFlags = null; - - /// - /// For each lane: Defines the allowed vehicle types - /// - internal static ExtVehicleType?[][] laneAllowedVehicleTypesArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index - - private static object laneSpeedLimitLock = new object(); - - internal static void PrintDebugInfo() { - Log.Info("------------------------"); - Log.Info("--- LANE ARROW FLAGS ---"); - Log.Info("------------------------"); - for (uint i = 0; i < laneArrowFlags.Length; ++i) { - if (highwayLaneArrowFlags[i] != null || laneArrowFlags[i] != null) { - Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}"); - } - - if (highwayLaneArrowFlags[i] != null) { - Log.Info($"\thighway arrows: {highwayLaneArrowFlags[i]}"); - } - - if (laneArrowFlags[i] != null) { - Log.Info($"\tcustom arrows: {laneArrowFlags[i]}"); - } - } - - Log.Info("------------------------"); - Log.Info("--- LANE CONNECTIONS ---"); - Log.Info("------------------------"); - for (uint i = 0; i < laneConnections.Length; ++i) { - if (laneConnections[i] == null) - continue; - - ushort segmentId = Singleton.instance.m_lanes.m_buffer[i].m_segment; - Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}, seg. valid? {Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)}"); - for (int x = 0; x < 2; ++x) { - if (laneConnections[i][x] == null) - continue; - - ushort nodeId = x == 0 ? Singleton.instance.m_segments.m_buffer[segmentId].m_startNode : Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; - Log.Info($"\tNode idx {x} ({nodeId}, seg. {segmentId}): valid? {Constants.ServiceFactory.NetService.IsNodeValid(nodeId)}"); - - for (int y = 0; y < laneConnections[i][x].Length; ++y) { - if (laneConnections[i][x][y] == 0) - continue; - - Log.Info($"\t\tEntry {y}: {laneConnections[i][x][y]} (valid? {Constants.ServiceFactory.NetService.IsLaneValid(laneConnections[i][x][y])})"); - } - } - } - - Log.Info("-------------------------"); - Log.Info("--- LANE SPEED LIMITS ---"); - Log.Info("-------------------------"); - for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { - if (laneSpeedLimitArray[i] == null) - continue; - Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); - for (int x = 0; x < laneSpeedLimitArray[i].Length; ++x) { - if (laneSpeedLimitArray[i][x] == null) - continue; - Log.Info($"\tLane idx {x}: {laneSpeedLimitArray[i][x]}"); - } - } - - Log.Info("---------------------------------"); - Log.Info("--- LANE VEHICLE RESTRICTIONS ---"); - Log.Info("---------------------------------"); - for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { - if (laneAllowedVehicleTypesArray[i] == null) - continue; - Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); - for (int x = 0; x < laneAllowedVehicleTypesArray[i].Length; ++x) { - if (laneAllowedVehicleTypesArray[i][x] == null) - continue; - Log.Info($"\tLane idx {x}: {laneAllowedVehicleTypesArray[i][x]}"); - } - } - } - - [Obsolete] - public static bool mayHaveTrafficLight(ushort nodeId) { - if (nodeId <= 0) { - return false; - } - - if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) { - //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (not created). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags}"); - Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; - return false; - } - - ItemClass connectionClass = Singleton.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); - if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) == NetNode.Flags.None && - connectionClass.m_service != ItemClass.Service.PublicTransport - ) { - //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no junction or not public transport). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags} connectionClass={connectionClass?.m_service}"); - Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; - return false; - } - - if (connectionClass == null || - (connectionClass.m_service != ItemClass.Service.Road && - connectionClass.m_service != ItemClass.Service.PublicTransport)) { - //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no connection class). connectionClass={connectionClass?.m_service}"); - Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; - return false; - } - - return true; - } - - [Obsolete] - public static bool setNodeTrafficLight(ushort nodeId, bool flag) { - if (nodeId <= 0) - return false; + using System; + using System.Collections.Generic; + using System.Threading; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Manager.Impl; + using Traffic.Enums; + + [Obsolete] + public class Flags { + public static readonly uint lfr = (uint)NetLane.Flags.LeftForwardRight; + + /// + /// For each lane: Defines the lane arrows which are set + /// + private static LaneArrows?[] laneArrowFlags = null; + + /// + /// For each lane (by id): list of lanes that are connected with this lane by the T++ lane connector + /// key 1: source lane id + /// key 2: at start node? + /// values: target lane id + /// + internal static uint[][][] laneConnections = null; + + /// + /// For each lane: Defines the currently set speed limit + /// + private static Dictionary laneSpeedLimit = null; // TODO remove + + internal static float?[][] laneSpeedLimitArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index + + /// + /// For each lane: Defines the lane arrows which are set in highway rule mode (they are not saved) + /// + private static LaneArrows?[] highwayLaneArrowFlags = null; + + /// + /// For each lane: Defines the allowed vehicle types + /// + internal static ExtVehicleType?[][] laneAllowedVehicleTypesArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index + + private static object laneSpeedLimitLock = new object(); + + internal static void PrintDebugInfo() { + Log.Info("------------------------"); + Log.Info("--- LANE ARROW FLAGS ---"); + Log.Info("------------------------"); + for (uint i = 0; i < laneArrowFlags.Length; ++i) { + if (highwayLaneArrowFlags[i] != null || laneArrowFlags[i] != null) { + Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}"); + } + + if (highwayLaneArrowFlags[i] != null) { + Log.Info($"\thighway arrows: {highwayLaneArrowFlags[i]}"); + } + + if (laneArrowFlags[i] != null) { + Log.Info($"\tcustom arrows: {laneArrowFlags[i]}"); + } + } + + Log.Info("------------------------"); + Log.Info("--- LANE CONNECTIONS ---"); + Log.Info("------------------------"); + for (uint i = 0; i < laneConnections.Length; ++i) { + if (laneConnections[i] == null) + continue; + + ushort segmentId = Singleton.instance.m_lanes.m_buffer[i].m_segment; + Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}, seg. valid? {Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)}"); + for (int x = 0; x < 2; ++x) { + if (laneConnections[i][x] == null) + continue; + + ushort nodeId = x == 0 ? Singleton.instance.m_segments.m_buffer[segmentId].m_startNode : Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; + Log.Info($"\tNode idx {x} ({nodeId}, seg. {segmentId}): valid? {Constants.ServiceFactory.NetService.IsNodeValid(nodeId)}"); + + for (int y = 0; y < laneConnections[i][x].Length; ++y) { + if (laneConnections[i][x][y] == 0) + continue; + + Log.Info($"\t\tEntry {y}: {laneConnections[i][x][y]} (valid? {Constants.ServiceFactory.NetService.IsLaneValid(laneConnections[i][x][y])})"); + } + } + } + + Log.Info("-------------------------"); + Log.Info("--- LANE SPEED LIMITS ---"); + Log.Info("-------------------------"); + for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { + if (laneSpeedLimitArray[i] == null) + continue; + Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); + for (int x = 0; x < laneSpeedLimitArray[i].Length; ++x) { + if (laneSpeedLimitArray[i][x] == null) + continue; + Log.Info($"\tLane idx {x}: {laneSpeedLimitArray[i][x]}"); + } + } + + Log.Info("---------------------------------"); + Log.Info("--- LANE VEHICLE RESTRICTIONS ---"); + Log.Info("---------------------------------"); + for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { + if (laneAllowedVehicleTypesArray[i] == null) + continue; + Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); + for (int x = 0; x < laneAllowedVehicleTypesArray[i].Length; ++x) { + if (laneAllowedVehicleTypesArray[i][x] == null) + continue; + Log.Info($"\tLane idx {x}: {laneAllowedVehicleTypesArray[i][x]}"); + } + } + } + + [Obsolete] + public static bool mayHaveTrafficLight(ushort nodeId) { + if (nodeId <= 0) { + return false; + } + + if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) { + //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (not created). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags}"); + Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; + return false; + } + + ItemClass connectionClass = Singleton.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); + if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) == NetNode.Flags.None && + connectionClass.m_service != ItemClass.Service.PublicTransport + ) { + //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no junction or not public transport). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags} connectionClass={connectionClass?.m_service}"); + Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; + return false; + } + + if (connectionClass == null || + (connectionClass.m_service != ItemClass.Service.Road && + connectionClass.m_service != ItemClass.Service.PublicTransport)) { + //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no connection class). connectionClass={connectionClass?.m_service}"); + Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; + return false; + } + + return true; + } + + [Obsolete] + public static bool setNodeTrafficLight(ushort nodeId, bool flag) { + if (nodeId <= 0) + return false; #if DEBUGFLAGS Log._Debug($"Flags: Set node traffic light: {nodeId}={flag}"); #endif - if (!mayHaveTrafficLight(nodeId)) { - //Log.Warning($"Flags: Refusing to add/delete traffic light to/from node: {nodeId} {flag}"); - return false; - } + if (!mayHaveTrafficLight(nodeId)) { + //Log.Warning($"Flags: Refusing to add/delete traffic light to/from node: {nodeId} {flag}"); + return false; + } - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - NetNode.Flags flags = node.m_flags | NetNode.Flags.CustomTrafficLights; - if ((bool)flag) { + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + NetNode.Flags flags = node.m_flags | NetNode.Flags.CustomTrafficLights; + if ((bool)flag) { #if DEBUGFLAGS Log._Debug($"Adding traffic light @ node {nId}"); #endif - flags |= NetNode.Flags.TrafficLights; - } else { + flags |= NetNode.Flags.TrafficLights; + } else { #if DEBUGFLAGS Log._Debug($"Removing traffic light @ node {nId}"); #endif - flags &= ~NetNode.Flags.TrafficLights; - } - node.m_flags = flags; - return true; - }); - return true; - } - - [Obsolete] - internal static bool isNodeTrafficLight(ushort nodeId) { - if (nodeId <= 0) - return false; - - if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) - return false; - - return (Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; - } - - /// - /// Removes lane connections that point from lane to lane at node . - /// - /// - /// - /// - /// - private static bool RemoveSingleLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { + flags &= ~NetNode.Flags.TrafficLights; + } + node.m_flags = flags; + return true; + }); + return true; + } + + [Obsolete] + internal static bool isNodeTrafficLight(ushort nodeId) { + if (nodeId <= 0) + return false; + + if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) + return false; + + return (Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; + } + + /// + /// Removes lane connections that point from lane to lane at node . + /// + /// + /// + /// + /// + private static bool RemoveSingleLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { #if DEBUGFLAGS Log._Debug($"Flags.CleanupLaneConnections({sourceLaneId}, {targetLaneId}, {startNode}) called."); #endif - int nodeArrayIndex = startNode ? 0 : 1; - - if (laneConnections[sourceLaneId] == null || laneConnections[sourceLaneId][nodeArrayIndex] == null) - return false; - - uint[] srcLaneConnections = laneConnections[sourceLaneId][nodeArrayIndex]; - - bool ret = false; - int remainingConnections = 0; - for (int i = 0; i < srcLaneConnections.Length; ++i) { - if (srcLaneConnections[i] != targetLaneId) { - ++remainingConnections; - } else { - ret = true; - srcLaneConnections[i] = 0; - } - } - - if (remainingConnections <= 0) { - laneConnections[sourceLaneId][nodeArrayIndex] = null; - if (laneConnections[sourceLaneId][1 - nodeArrayIndex] == null) - laneConnections[sourceLaneId] = null; // total cleanup - return ret; - } - - if (remainingConnections != srcLaneConnections.Length) { - laneConnections[sourceLaneId][nodeArrayIndex] = new uint[remainingConnections]; - int k = 0; - for (int i = 0; i < srcLaneConnections.Length; ++i) { - if (srcLaneConnections[i] == 0) - continue; - laneConnections[sourceLaneId][nodeArrayIndex][k++] = srcLaneConnections[i]; - } - } - return ret; - } - - /// - /// Removes any lane connections that exist between two given lanes - /// - /// - /// - /// - /// - internal static bool RemoveLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { + int nodeArrayIndex = startNode ? 0 : 1; + + if (laneConnections[sourceLaneId] == null || laneConnections[sourceLaneId][nodeArrayIndex] == null) + return false; + + uint[] srcLaneConnections = laneConnections[sourceLaneId][nodeArrayIndex]; + + bool ret = false; + int remainingConnections = 0; + for (int i = 0; i < srcLaneConnections.Length; ++i) { + if (srcLaneConnections[i] != targetLaneId) { + ++remainingConnections; + } else { + ret = true; + srcLaneConnections[i] = 0; + } + } + + if (remainingConnections <= 0) { + laneConnections[sourceLaneId][nodeArrayIndex] = null; + if (laneConnections[sourceLaneId][1 - nodeArrayIndex] == null) + laneConnections[sourceLaneId] = null; // total cleanup + return ret; + } + + if (remainingConnections != srcLaneConnections.Length) { + laneConnections[sourceLaneId][nodeArrayIndex] = new uint[remainingConnections]; + int k = 0; + for (int i = 0; i < srcLaneConnections.Length; ++i) { + if (srcLaneConnections[i] == 0) + continue; + laneConnections[sourceLaneId][nodeArrayIndex][k++] = srcLaneConnections[i]; + } + } + return ret; + } + + /// + /// Removes any lane connections that exist between two given lanes + /// + /// + /// + /// + /// + internal static bool RemoveLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}) called."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}) called."); #endif - bool lane1Valid = CheckLane(lane1Id); - bool lane2Valid = CheckLane(lane2Id); - - bool ret = false; - - if (! lane1Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane1Id); - ret = true; - } - - if (! lane2Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane2Id); - ret = true; - } - - if (lane1Valid || lane2Valid) { - ushort commonNodeId; - bool startNode2; - - LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor - if (commonNodeId == 0) { - Log.Warning($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}): Could not identify common node between lanes {lane1Id} and {lane2Id}"); - } - - if (RemoveSingleLaneConnection(lane1Id, lane2Id, startNode1)) - ret = true; - if (RemoveSingleLaneConnection(lane2Id, lane1Id, startNode2)) - ret = true; - } + bool lane1Valid = CheckLane(lane1Id); + bool lane2Valid = CheckLane(lane2Id); + + bool ret = false; + + if (! lane1Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane1Id); + ret = true; + } + + if (! lane2Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane2Id); + ret = true; + } + + if (lane1Valid || lane2Valid) { + ushort commonNodeId; + bool startNode2; + + LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor + if (commonNodeId == 0) { + Log.Warning($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}): Could not identify common node between lanes {lane1Id} and {lane2Id}"); + } + + if (RemoveSingleLaneConnection(lane1Id, lane2Id, startNode1)) + ret = true; + if (RemoveSingleLaneConnection(lane2Id, lane1Id, startNode2)) + ret = true; + } #if DEBUGCONN - if (debug) - Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}). ret={ret}"); + if (debug) + Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}). ret={ret}"); #endif - return ret; - } - - /// - /// Removes all incoming/outgoing lane connections of the given lane - /// - /// - /// - internal static void RemoveLaneConnections(uint laneId, bool? startNode=null) { + return ret; + } + + /// + /// Removes all incoming/outgoing lane connections of the given lane + /// + /// + /// + internal static void RemoveLaneConnections(uint laneId, bool? startNode=null) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}) called. laneConnections[{laneId}]={laneConnections[laneId]}"); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}) called. laneConnections[{laneId}]={laneConnections[laneId]}"); #endif - if (laneConnections[laneId] == null) - return; + if (laneConnections[laneId] == null) + return; - bool laneValid = CheckLane(laneId); - bool clearBothSides = startNode == null || !laneValid; + bool laneValid = CheckLane(laneId); + bool clearBothSides = startNode == null || !laneValid; #if DEBUGCONN - if (debug) - Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}): laneValid={laneValid}, clearBothSides={clearBothSides}"); + if (debug) + Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}): laneValid={laneValid}, clearBothSides={clearBothSides}"); #endif - int? nodeArrayIndex = null; - if (!clearBothSides) { - nodeArrayIndex = (bool)startNode ? 0 : 1; - } - - for (int k = 0; k <= 1; ++k) { - if (nodeArrayIndex != null && k != (int)nodeArrayIndex) - continue; - - bool startNode1 = k == 0; - - if (laneConnections[laneId][k] == null) - continue; - - for (int i = 0; i < laneConnections[laneId][k].Length; ++i) { - uint otherLaneId = laneConnections[laneId][k][i]; - ushort commonNodeId; - bool startNode2; - LaneConnectionManager.Instance.GetCommonNodeId(laneId, otherLaneId, startNode1, out commonNodeId, out startNode2); // TODO refactor - if (commonNodeId == 0) { - Log.Warning($"Flags.RemoveLaneConnections({laneId}, {startNode}): Could not identify common node between lanes {laneId} and {otherLaneId}"); - } - - RemoveSingleLaneConnection(otherLaneId, laneId, startNode2); - } - - laneConnections[laneId][k] = null; - } - - if (clearBothSides) - laneConnections[laneId] = null; - } - - /// - /// adds lane connections between two given lanes - /// - /// - /// - /// - /// - internal static bool AddLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { - bool lane1Valid = CheckLane(lane1Id); - bool lane2Valid = CheckLane(lane2Id); - - if (!lane1Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane1Id); - } - - if (!lane2Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane2Id); - } - - if (!lane1Valid || !lane2Valid) - return false; - - ushort commonNodeId; - bool startNode2; - LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor - - if (commonNodeId != 0) { - CreateLaneConnection(lane1Id, lane2Id, startNode1); - CreateLaneConnection(lane2Id, lane1Id, startNode2); - - return true; - } else - return false; - } - - /// - /// Adds a lane connection from lane to lane at node - /// Assumes that both lanes are valid. - /// - /// - /// - /// - private static void CreateLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { - if (laneConnections[sourceLaneId] == null) { - laneConnections[sourceLaneId] = new uint[2][]; - } - - int nodeArrayIndex = startNode ? 0 : 1; - - if (laneConnections[sourceLaneId][nodeArrayIndex] == null) { - laneConnections[sourceLaneId][nodeArrayIndex] = new uint[] { targetLaneId }; - return; - } - - uint[] oldConnections = laneConnections[sourceLaneId][nodeArrayIndex]; - laneConnections[sourceLaneId][nodeArrayIndex] = new uint[oldConnections.Length + 1]; - Array.Copy(oldConnections, laneConnections[sourceLaneId][nodeArrayIndex], oldConnections.Length); - laneConnections[sourceLaneId][nodeArrayIndex][oldConnections.Length] = targetLaneId; - } - - internal static bool CheckLane(uint laneId) { // TODO refactor - if (laneId <= 0) - return false; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return false; - - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - if (segmentId <= 0) - return false; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return false; - return true; - } - - public static void setLaneSpeedLimit(uint laneId, float? speedLimit) { - if (!CheckLane(laneId)) { - return; - } - - var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - - var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - var curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - uint laneIndex = 0; - while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { - if (curLaneId == laneId) { - setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); - return; - } - laneIndex++; - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - } - } - - public static void removeLaneSpeedLimit(uint laneId) { - setLaneSpeedLimit(laneId, null); - } - - public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, float speedLimit) { - if (segmentId <= 0 || laneIndex < 0) { - return; - } - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & - (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { - return; - } - var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - if (laneIndex >= segmentInfo.m_lanes.Length) { - return; - } - - // find the lane id - var laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - for (var i = 0; i < laneIndex; ++i) { - if (laneId == 0) { - return; // no valid lane found - } - laneId = Singleton.instance.m_lanes.m_buffer[laneId].m_nextLane; - } - - setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); - } - - public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint laneId, float? speedLimit) { - if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) { - return; - } - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { - return; - } - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) { - return; - } - var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - if (laneIndex >= segmentInfo.m_lanes.Length) { - return; - } - - try { - Monitor.Enter(laneSpeedLimitLock); + int? nodeArrayIndex = null; + if (!clearBothSides) { + nodeArrayIndex = (bool)startNode ? 0 : 1; + } + + for (int k = 0; k <= 1; ++k) { + if (nodeArrayIndex != null && k != (int)nodeArrayIndex) + continue; + + bool startNode1 = k == 0; + + if (laneConnections[laneId][k] == null) + continue; + + for (int i = 0; i < laneConnections[laneId][k].Length; ++i) { + uint otherLaneId = laneConnections[laneId][k][i]; + ushort commonNodeId; + bool startNode2; + LaneConnectionManager.Instance.GetCommonNodeId(laneId, otherLaneId, startNode1, out commonNodeId, out startNode2); // TODO refactor + if (commonNodeId == 0) { + Log.Warning($"Flags.RemoveLaneConnections({laneId}, {startNode}): Could not identify common node between lanes {laneId} and {otherLaneId}"); + } + + RemoveSingleLaneConnection(otherLaneId, laneId, startNode2); + } + + laneConnections[laneId][k] = null; + } + + if (clearBothSides) + laneConnections[laneId] = null; + } + + /// + /// adds lane connections between two given lanes + /// + /// + /// + /// + /// + internal static bool AddLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { + bool lane1Valid = CheckLane(lane1Id); + bool lane2Valid = CheckLane(lane2Id); + + if (!lane1Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane1Id); + } + + if (!lane2Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane2Id); + } + + if (!lane1Valid || !lane2Valid) + return false; + + ushort commonNodeId; + bool startNode2; + LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor + + if (commonNodeId != 0) { + CreateLaneConnection(lane1Id, lane2Id, startNode1); + CreateLaneConnection(lane2Id, lane1Id, startNode2); + + return true; + } else + return false; + } + + /// + /// Adds a lane connection from lane to lane at node + /// Assumes that both lanes are valid. + /// + /// + /// + /// + private static void CreateLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { + if (laneConnections[sourceLaneId] == null) { + laneConnections[sourceLaneId] = new uint[2][]; + } + + int nodeArrayIndex = startNode ? 0 : 1; + + if (laneConnections[sourceLaneId][nodeArrayIndex] == null) { + laneConnections[sourceLaneId][nodeArrayIndex] = new uint[] { targetLaneId }; + return; + } + + uint[] oldConnections = laneConnections[sourceLaneId][nodeArrayIndex]; + laneConnections[sourceLaneId][nodeArrayIndex] = new uint[oldConnections.Length + 1]; + Array.Copy(oldConnections, laneConnections[sourceLaneId][nodeArrayIndex], oldConnections.Length); + laneConnections[sourceLaneId][nodeArrayIndex][oldConnections.Length] = targetLaneId; + } + + internal static bool CheckLane(uint laneId) { // TODO refactor + if (laneId <= 0) + return false; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return false; + + ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + if (segmentId <= 0) + return false; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return false; + return true; + } + + public static void setLaneSpeedLimit(uint laneId, float? speedLimit) { + if (!CheckLane(laneId)) { + return; + } + + var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + var curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + uint laneIndex = 0; + while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { + if (curLaneId == laneId) { + setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); + return; + } + laneIndex++; + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + } + } + + public static void removeLaneSpeedLimit(uint laneId) { + setLaneSpeedLimit(laneId, null); + } + + public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, float speedLimit) { + if (segmentId <= 0 || laneIndex < 0) { + return; + } + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & + (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { + return; + } + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneIndex >= segmentInfo.m_lanes.Length) { + return; + } + + // find the lane id + var laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + for (var i = 0; i < laneIndex; ++i) { + if (laneId == 0) { + return; // no valid lane found + } + laneId = Singleton.instance.m_lanes.m_buffer[laneId].m_nextLane; + } + + setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); + } + + public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint laneId, float? speedLimit) { + if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) { + return; + } + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { + return; + } + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) { + return; + } + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneIndex >= segmentInfo.m_lanes.Length) { + return; + } + + try { + Monitor.Enter(laneSpeedLimitLock); #if DEBUGFLAGS Log._Debug($"Flags.setLaneSpeedLimit: setting speed limit of lane index {laneIndex} @ seg. {segmentId} to {speedLimit}"); #endif - if (speedLimit == null) { - laneSpeedLimit.Remove(laneId); - - if (laneSpeedLimitArray[segmentId] == null) { - return; - } - if (laneIndex >= laneSpeedLimitArray[segmentId].Length) { - return; - } - laneSpeedLimitArray[segmentId][laneIndex] = null; - } else { - laneSpeedLimit[laneId] = speedLimit.Value; - - // save speed limit into the fast-access array. - // (1) ensure that the array is defined and large enough - if (laneSpeedLimitArray[segmentId] == null) { - laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; - } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) { - var oldArray = laneSpeedLimitArray[segmentId]; - laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; - Array.Copy(oldArray, laneSpeedLimitArray[segmentId], oldArray.Length); - } - // (2) insert the custom speed limit - laneSpeedLimitArray[segmentId][laneIndex] = speedLimit; - } - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - } - - public static void setLaneAllowedVehicleTypes(uint laneId, ExtVehicleType vehicleTypes) { - if (laneId <= 0) - return; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return; - - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - if (segmentId <= 0) - return; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return; - - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - uint laneIndex = 0; - while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { - if (curLaneId == laneId) { - setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, vehicleTypes); - return; - } - laneIndex++; - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - } - } - - public static void setLaneAllowedVehicleTypes(ushort segmentId, uint laneIndex, uint laneId, ExtVehicleType vehicleTypes) { - if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) - return; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return; - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - if (laneIndex >= segmentInfo.m_lanes.Length) { - return; - } + if (speedLimit == null) { + laneSpeedLimit.Remove(laneId); + + if (laneSpeedLimitArray[segmentId] == null) { + return; + } + if (laneIndex >= laneSpeedLimitArray[segmentId].Length) { + return; + } + laneSpeedLimitArray[segmentId][laneIndex] = null; + } else { + laneSpeedLimit[laneId] = speedLimit.Value; + + // save speed limit into the fast-access array. + // (1) ensure that the array is defined and large enough + if (laneSpeedLimitArray[segmentId] == null) { + laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; + } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) { + var oldArray = laneSpeedLimitArray[segmentId]; + laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; + Array.Copy(oldArray, laneSpeedLimitArray[segmentId], oldArray.Length); + } + // (2) insert the custom speed limit + laneSpeedLimitArray[segmentId][laneIndex] = speedLimit; + } + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + } + + public static void setLaneAllowedVehicleTypes(uint laneId, ExtVehicleType vehicleTypes) { + if (laneId <= 0) + return; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return; + + ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + if (segmentId <= 0) + return; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return; + + NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + uint laneIndex = 0; + while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { + if (curLaneId == laneId) { + setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, vehicleTypes); + return; + } + laneIndex++; + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + } + } + + public static void setLaneAllowedVehicleTypes(ushort segmentId, uint laneIndex, uint laneId, API.Traffic.Enums.ExtVehicleType vehicleTypes) { + if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) + return; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return; + NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneIndex >= segmentInfo.m_lanes.Length) { + return; + } #if DEBUGFLAGS Log._Debug($"Flags.setLaneAllowedVehicleTypes: setting allowed vehicles of lane index {laneIndex} @ seg. {segmentId} to {vehicleTypes.ToString()}"); #endif - // save allowed vehicle types into the fast-access array. - // (1) ensure that the array is defined and large enough - if (laneAllowedVehicleTypesArray[segmentId] == null) { - laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; - } else if (laneAllowedVehicleTypesArray[segmentId].Length < segmentInfo.m_lanes.Length) { - var oldArray = laneAllowedVehicleTypesArray[segmentId]; - laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; - Array.Copy(oldArray, laneAllowedVehicleTypesArray[segmentId], oldArray.Length); - } - // (2) insert the custom speed limit - laneAllowedVehicleTypesArray[segmentId][laneIndex] = vehicleTypes; - } - - public static void resetSegmentVehicleRestrictions(ushort segmentId) { - if (segmentId <= 0) - return; + // save allowed vehicle types into the fast-access array. + // (1) ensure that the array is defined and large enough + if (laneAllowedVehicleTypesArray[segmentId] == null) { + laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; + } else if (laneAllowedVehicleTypesArray[segmentId].Length < segmentInfo.m_lanes.Length) { + var oldArray = laneAllowedVehicleTypesArray[segmentId]; + laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; + Array.Copy(oldArray, laneAllowedVehicleTypesArray[segmentId], oldArray.Length); + } + // (2) insert the custom speed limit + laneAllowedVehicleTypesArray[segmentId][laneIndex] = vehicleTypes; + } + + public static void resetSegmentVehicleRestrictions(ushort segmentId) { + if (segmentId <= 0) + return; #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentVehicleRestrictions: Resetting vehicle restrictions of segment {segmentId}."); #endif - laneAllowedVehicleTypesArray[segmentId] = null; - } + laneAllowedVehicleTypesArray[segmentId] = null; + } - public static void resetSegmentArrowFlags(ushort segmentId) { - if (segmentId <= 0) - return; + public static void resetSegmentArrowFlags(ushort segmentId) { + if (segmentId <= 0) + return; #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentArrowFlags: Resetting lane arrows of segment {segmentId}."); #endif - NetManager netManager = Singleton.instance; - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + NetManager netManager = Singleton.instance; + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - int numLanes = segmentInfo.m_lanes.Length; - int laneIndex = 0; - while (laneIndex < numLanes && curLaneId != 0u) { + uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + int numLanes = segmentInfo.m_lanes.Length; + int laneIndex = 0; + while (laneIndex < numLanes && curLaneId != 0u) { #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentArrowFlags: Resetting lane arrows of segment {segmentId}: Resetting lane {curLaneId}."); #endif - laneArrowFlags[curLaneId] = null; + laneArrowFlags[curLaneId] = null; - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + } - public static bool setLaneArrowFlags(uint laneId, LaneArrows flags, bool overrideHighwayArrows=false) { + public static bool setLaneArrowFlags(uint laneId, LaneArrows flags, bool overrideHighwayArrows=false) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}) called"); #endif - if (!mayHaveLaneArrows(laneId)) { + if (!mayHaveLaneArrows(laneId)) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): lane must not have lane arrows"); #endif - removeLaneArrowFlags(laneId); - return false; - } + removeLaneArrowFlags(laneId); + return false; + } - if (!overrideHighwayArrows && highwayLaneArrowFlags[laneId] != null) { + if (!overrideHighwayArrows && highwayLaneArrowFlags[laneId] != null) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): highway arrows may not be overridden"); #endif - return false; // disallow custom lane arrows in highway rule mode - } + return false; // disallow custom lane arrows in highway rule mode + } - if (overrideHighwayArrows) { + if (overrideHighwayArrows) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): overriding highway arrows"); #endif - highwayLaneArrowFlags[laneId] = null; - } + highwayLaneArrowFlags[laneId] = null; + } #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): setting flags"); #endif - laneArrowFlags[laneId] = flags; - return applyLaneArrowFlags(laneId, false); - } - - public static void setHighwayLaneArrowFlags(uint laneId, LaneArrows flags, bool check=true) { - if (check && !mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - return; - } - - highwayLaneArrowFlags[laneId] = flags; + laneArrowFlags[laneId] = flags; + return applyLaneArrowFlags(laneId, false); + } + + public static void setHighwayLaneArrowFlags(uint laneId, LaneArrows flags, bool check=true) { + if (check && !mayHaveLaneArrows(laneId)) { + removeLaneArrowFlags(laneId); + return; + } + + highwayLaneArrowFlags[laneId] = flags; #if DEBUGFLAGS Log._Debug($"Flags.setHighwayLaneArrowFlags: Setting highway arrows of lane {laneId} to {flags}"); #endif - applyLaneArrowFlags(laneId, false); - } - - public static bool toggleLaneArrowFlags(uint laneId, bool startNode, LaneArrows flags, out SetLaneArrowUnableReason res) { - if (!mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - res = SetLaneArrowUnableReason.Invalid; - return false; - } - - if (highwayLaneArrowFlags[laneId] != null) { - res = SetLaneArrowUnableReason.HighwayArrows; - return false; // disallow custom lane arrows in highway rule mode - } - - if (LaneConnectionManager.Instance.HasConnections(laneId, startNode)) { // TODO refactor - res = SetLaneArrowUnableReason.LaneConnection; - return false; // custom lane connection present - } - - LaneArrows? arrows = laneArrowFlags[laneId]; - if (arrows == null) { - // read currently defined arrows - uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; - laneFlags &= lfr; // filter arrows - arrows = (LaneArrows)laneFlags; - } - - arrows ^= flags; - laneArrowFlags[laneId] = arrows; - if (applyLaneArrowFlags(laneId, false)) { - res = SetLaneArrowUnableReason.Success; - return true; - } else { - res = SetLaneArrowUnableReason.Invalid; - return false; - } - } - - internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { - if (laneId <= 0) - return false; - NetManager netManager = Singleton.instance; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return false; - - ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; - - var dir = NetInfo.Direction.Forward; - var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); - - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - int numLanes = segmentInfo.m_lanes.Length; - int laneIndex = 0; - int wIter = 0; - while (laneIndex < numLanes && curLaneId != 0u) { - ++wIter; - if (wIter >= 100) { - Log.Error("Too many iterations in Flags.mayHaveLaneArrows!"); - break; - } - - if (curLaneId == laneId) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - bool isStartNode = (laneInfo.m_finalDirection & dir2) == NetInfo.Direction.None; - if (startNode != null && isStartNode != startNode) - return false; - ushort nodeId = isStartNode ? netManager.m_segments.m_buffer[segmentId].m_startNode : netManager.m_segments.m_buffer[segmentId].m_endNode; - - if ((netManager.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) - return false; - return (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; - } - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - return false; - } - - public static float? getLaneSpeedLimit(uint laneId) { - try { - Monitor.Enter(laneSpeedLimitLock); - - float speedLimit; - if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out speedLimit)) { - return null; - } - - return speedLimit; - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - } - - internal static IDictionary getAllLaneSpeedLimits() { - IDictionary ret = new Dictionary(); - try { - Monitor.Enter(laneSpeedLimitLock); - - ret = new Dictionary(laneSpeedLimit); - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - return ret; - } - - internal static IDictionary getAllLaneAllowedVehicleTypes() { - IDictionary ret = new Dictionary(); - - for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { - Constants.ServiceFactory.NetService.ProcessSegment((ushort)segmentId, delegate (ushort segId, ref NetSegment segment) { - if ((segment.m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return true; - - ExtVehicleType?[] allowedTypes = laneAllowedVehicleTypesArray[segId]; - if (allowedTypes == null) { - return true; - } - - Constants.ServiceFactory.NetService.IterateSegmentLanes(segId, ref segment, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort sId, ref NetSegment seg, byte laneIndex) { - if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.None) { - return true; - } - - if (laneIndex >= allowedTypes.Length) { - return true; - } - - ExtVehicleType? allowedType = allowedTypes[laneIndex]; - - if (allowedType == null) { - return true; - } - - ret.Add(laneId, (ExtVehicleType)allowedType); - return true; - }); - return true; - }); - } - - return ret; - } - - public static LaneArrows? getLaneArrowFlags(uint laneId) { - return laneArrowFlags[laneId]; - } - - public static LaneArrows? getHighwayLaneArrowFlags(uint laneId) { - return highwayLaneArrowFlags[laneId]; - } - - public static void removeHighwayLaneArrowFlags(uint laneId) { + applyLaneArrowFlags(laneId, false); + } + + public static bool toggleLaneArrowFlags(uint laneId, bool startNode, LaneArrows flags, out SetLaneArrowUnableReason res) { + if (!mayHaveLaneArrows(laneId)) { + removeLaneArrowFlags(laneId); + res = SetLaneArrowUnableReason.Invalid; + return false; + } + + if (highwayLaneArrowFlags[laneId] != null) { + res = SetLaneArrowUnableReason.HighwayArrows; + return false; // disallow custom lane arrows in highway rule mode + } + + if (LaneConnectionManager.Instance.HasConnections(laneId, startNode)) { // TODO refactor + res = SetLaneArrowUnableReason.LaneConnection; + return false; // custom lane connection present + } + + LaneArrows? arrows = laneArrowFlags[laneId]; + if (arrows == null) { + // read currently defined arrows + uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; + laneFlags &= lfr; // filter arrows + arrows = (LaneArrows)laneFlags; + } + + arrows ^= flags; + laneArrowFlags[laneId] = arrows; + if (applyLaneArrowFlags(laneId, false)) { + res = SetLaneArrowUnableReason.Success; + return true; + } else { + res = SetLaneArrowUnableReason.Invalid; + return false; + } + } + + internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { + if (laneId <= 0) + return false; + NetManager netManager = Singleton.instance; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return false; + + ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; + + var dir = NetInfo.Direction.Forward; + var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); + + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + int numLanes = segmentInfo.m_lanes.Length; + int laneIndex = 0; + int wIter = 0; + while (laneIndex < numLanes && curLaneId != 0u) { + ++wIter; + if (wIter >= 100) { + Log.Error("Too many iterations in Flags.mayHaveLaneArrows!"); + break; + } + + if (curLaneId == laneId) { + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + bool isStartNode = (laneInfo.m_finalDirection & dir2) == NetInfo.Direction.None; + if (startNode != null && isStartNode != startNode) + return false; + ushort nodeId = isStartNode ? netManager.m_segments.m_buffer[segmentId].m_startNode : netManager.m_segments.m_buffer[segmentId].m_endNode; + + if ((netManager.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) + return false; + return (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; + } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + return false; + } + + public static float? getLaneSpeedLimit(uint laneId) { + try { + Monitor.Enter(laneSpeedLimitLock); + + float speedLimit; + if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out speedLimit)) { + return null; + } + + return speedLimit; + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + } + + internal static IDictionary getAllLaneSpeedLimits() { + IDictionary ret = new Dictionary(); + try { + Monitor.Enter(laneSpeedLimitLock); + + ret = new Dictionary(laneSpeedLimit); + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + return ret; + } + + internal static IDictionary getAllLaneAllowedVehicleTypes() { + IDictionary ret = new Dictionary(); + + for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { + Constants.ServiceFactory.NetService.ProcessSegment((ushort)segmentId, delegate (ushort segId, ref NetSegment segment) { + if ((segment.m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return true; + + ExtVehicleType?[] allowedTypes = laneAllowedVehicleTypesArray[segId]; + if (allowedTypes == null) { + return true; + } + + Constants.ServiceFactory.NetService.IterateSegmentLanes(segId, ref segment, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort sId, ref NetSegment seg, byte laneIndex) { + if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.None) { + return true; + } + + if (laneIndex >= allowedTypes.Length) { + return true; + } + + ExtVehicleType? allowedType = allowedTypes[laneIndex]; + + if (allowedType == null) { + return true; + } + + ret.Add(laneId, (ExtVehicleType)allowedType); + return true; + }); + return true; + }); + } + + return ret; + } + + public static LaneArrows? getLaneArrowFlags(uint laneId) { + return laneArrowFlags[laneId]; + } + + public static LaneArrows? getHighwayLaneArrowFlags(uint laneId) { + return highwayLaneArrowFlags[laneId]; + } + + public static void removeHighwayLaneArrowFlags(uint laneId) { #if DEBUGFLAGS Log._Debug($"Flags.removeHighwayLaneArrowFlags: Removing highway arrows of lane {laneId}"); #endif - if (highwayLaneArrowFlags[laneId] != null) { - highwayLaneArrowFlags[laneId] = null; - applyLaneArrowFlags(laneId, false); - } - } - - public static void applyAllFlags() { - for (uint i = 0; i < laneArrowFlags.Length; ++i) { - applyLaneArrowFlags(i); - } - } - - public static bool applyLaneArrowFlags(uint laneId, bool check=true) { + if (highwayLaneArrowFlags[laneId] != null) { + highwayLaneArrowFlags[laneId] = null; + applyLaneArrowFlags(laneId, false); + } + } + + public static void applyAllFlags() { + for (uint i = 0; i < laneArrowFlags.Length; ++i) { + applyLaneArrowFlags(i); + } + } + + public static bool applyLaneArrowFlags(uint laneId, bool check=true) { #if DEBUGFLAGS Log._Debug($"Flags.applyLaneArrowFlags({laneId}, {check}) called"); #endif - if (laneId <= 0) - return true; + if (laneId <= 0) + return true; - if (check && !mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - return false; - } + if (check && !mayHaveLaneArrows(laneId)) { + removeLaneArrowFlags(laneId); + return false; + } - LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; - LaneArrows? arrows = laneArrowFlags[laneId]; - uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; + LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; + LaneArrows? arrows = laneArrowFlags[laneId]; + uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; - if (hwArrows != null) { - laneFlags &= ~lfr; // remove all arrows - laneFlags |= (uint)hwArrows; // add highway arrows - } else if (arrows != null) { - LaneArrows flags = (LaneArrows)arrows; - laneFlags &= ~lfr; // remove all arrows - laneFlags |= (uint)flags; // add desired arrows - } + if (hwArrows != null) { + laneFlags &= ~lfr; // remove all arrows + laneFlags |= (uint)hwArrows; // add highway arrows + } else if (arrows != null) { + LaneArrows flags = (LaneArrows)arrows; + laneFlags &= ~lfr; // remove all arrows + laneFlags |= (uint)flags; // add desired arrows + } #if DEBUGFLAGS Log._Debug($"Flags.applyLaneArrowFlags: Setting lane flags of lane {laneId} to {((NetLane.Flags)laneFlags).ToString()}"); #endif - Singleton.instance.m_lanes.m_buffer[laneId].m_flags = Convert.ToUInt16(laneFlags); - return true; - } + Singleton.instance.m_lanes.m_buffer[laneId].m_flags = Convert.ToUInt16(laneFlags); + return true; + } - public static LaneArrows getFinalLaneArrowFlags(uint laneId, bool check=true) { - if (! mayHaveLaneArrows(laneId)) { + public static LaneArrows getFinalLaneArrowFlags(uint laneId, bool check=true) { + if (! mayHaveLaneArrows(laneId)) { #if DEBUGFLAGS Log._Debug($"Lane {laneId} may not have lane arrows"); #endif - return LaneArrows.None; - } - - uint ret = 0; - LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; - LaneArrows? arrows = laneArrowFlags[laneId]; - - if (hwArrows != null) { - ret &= ~lfr; // remove all arrows - ret |= (uint)hwArrows; // add highway arrows - } else if (arrows != null) { - LaneArrows flags = (LaneArrows)arrows; - ret &= ~lfr; // remove all arrows - ret |= (uint)flags; // add desired arrows - } else { - Constants.ServiceFactory.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { - ret = lane.m_flags; - ret &= (uint)LaneArrows.LeftForwardRight; - return true; - }); - } - - return (LaneArrows)ret; - } - - public static void removeLaneArrowFlags(uint laneId) { - if (laneId <= 0) - return; - - if (highwayLaneArrowFlags[laneId] != null) - return; // modification of arrows in highway rule mode is forbidden - - laneArrowFlags[laneId] = null; - uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; - - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) == NetLane.Flags.Created) { - Singleton.instance.m_lanes.m_buffer[laneId].m_flags &= (ushort)~lfr; - } - } - - internal static void removeHighwayLaneArrowFlagsAtSegment(ushort segmentId) { - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return; - - int i = 0; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - - while (i < Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes.Length && curLaneId != 0u) { - Flags.removeHighwayLaneArrowFlags(curLaneId); - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - ++i; - } // foreach lane - } - - public static void clearHighwayLaneArrows() { - for (uint i = 0; i < Singleton.instance.m_lanes.m_size; ++i) { - highwayLaneArrowFlags[i] = null; - } - } - - public static void resetSpeedLimits() { - try { - Monitor.Enter(laneSpeedLimitLock); - laneSpeedLimit.Clear(); - for (int i = 0; i < Singleton.instance.m_segments.m_size; ++i) { - laneSpeedLimitArray[i] = null; - } - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - } - - internal static void OnLevelUnloading() { - for (uint i = 0; i < laneConnections.Length; ++i) { - laneConnections[i] = null; - } - - for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { - laneSpeedLimitArray[i] = null; - } - - try { - Monitor.Enter(laneSpeedLimitLock); - laneSpeedLimit.Clear(); - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - - for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { - laneAllowedVehicleTypesArray[i] = null; - } - - for (uint i = 0; i < laneArrowFlags.Length; ++i) { - laneArrowFlags[i] = null; - } - - for (uint i = 0; i < highwayLaneArrowFlags.Length; ++i) { - highwayLaneArrowFlags[i] = null; - } - } - - static Flags() { - laneConnections = new uint[NetManager.MAX_LANE_COUNT][][]; - laneSpeedLimitArray = new float?[NetManager.MAX_SEGMENT_COUNT][]; - laneSpeedLimit = new Dictionary(); - laneAllowedVehicleTypesArray = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][]; - laneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; - highwayLaneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; - } - - public static void OnBeforeLoadData() { - - } - } -} + return LaneArrows.None; + } + + uint ret = 0; + LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; + LaneArrows? arrows = laneArrowFlags[laneId]; + + if (hwArrows != null) { + ret &= ~lfr; // remove all arrows + ret |= (uint)hwArrows; // add highway arrows + } else if (arrows != null) { + LaneArrows flags = (LaneArrows)arrows; + ret &= ~lfr; // remove all arrows + ret |= (uint)flags; // add desired arrows + } else { + Constants.ServiceFactory.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { + ret = lane.m_flags; + ret &= (uint)LaneArrows.LeftForwardRight; + return true; + }); + } + + return (LaneArrows)ret; + } + + public static void removeLaneArrowFlags(uint laneId) { + if (laneId <= 0) + return; + + if (highwayLaneArrowFlags[laneId] != null) + return; // modification of arrows in highway rule mode is forbidden + + laneArrowFlags[laneId] = null; + uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; + + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) == NetLane.Flags.Created) { + Singleton.instance.m_lanes.m_buffer[laneId].m_flags &= (ushort)~lfr; + } + } + + internal static void removeHighwayLaneArrowFlagsAtSegment(ushort segmentId) { + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return; + + int i = 0; + uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + + while (i < Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes.Length && curLaneId != 0u) { + Flags.removeHighwayLaneArrowFlags(curLaneId); + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + ++i; + } // foreach lane + } + + public static void clearHighwayLaneArrows() { + for (uint i = 0; i < Singleton.instance.m_lanes.m_size; ++i) { + highwayLaneArrowFlags[i] = null; + } + } + + public static void resetSpeedLimits() { + try { + Monitor.Enter(laneSpeedLimitLock); + laneSpeedLimit.Clear(); + for (int i = 0; i < Singleton.instance.m_segments.m_size; ++i) { + laneSpeedLimitArray[i] = null; + } + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + } + + internal static void OnLevelUnloading() { + for (uint i = 0; i < laneConnections.Length; ++i) { + laneConnections[i] = null; + } + + for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { + laneSpeedLimitArray[i] = null; + } + + try { + Monitor.Enter(laneSpeedLimitLock); + laneSpeedLimit.Clear(); + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + + for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { + laneAllowedVehicleTypesArray[i] = null; + } + + for (uint i = 0; i < laneArrowFlags.Length; ++i) { + laneArrowFlags[i] = null; + } + + for (uint i = 0; i < highwayLaneArrowFlags.Length; ++i) { + highwayLaneArrowFlags[i] = null; + } + } + + static Flags() { + laneConnections = new uint[NetManager.MAX_LANE_COUNT][][]; + laneSpeedLimitArray = new float?[NetManager.MAX_SEGMENT_COUNT][]; + laneSpeedLimit = new Dictionary(); + laneAllowedVehicleTypesArray = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][]; + laneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; + highwayLaneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; + } + + public static void OnBeforeLoadData() { + + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index bcbe79d47..15e86f6cb 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -13,6 +13,7 @@ using TrafficManager.Traffic.Data; namespace TrafficManager.State { + using API.Traffic.Enums; public class Options : MonoBehaviour { private static UIDropDown languageDropdown = null; diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index db4e16741..264878c0e 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -213,6 +213,7 @@ + diff --git a/TLM/TLM/Traffic/ExtVehicleType.cs b/TLM/TLM/Traffic/ExtVehicleType.cs new file mode 100644 index 000000000..5feda1659 --- /dev/null +++ b/TLM/TLM/Traffic/ExtVehicleType.cs @@ -0,0 +1,53 @@ +namespace TrafficManager.Traffic { + using System; + + /// + /// This Enum is kept for save compatibility. + /// DO NOT USE. + /// Please use TMPE.API.Traffic.Enums.ExtVehicleType + /// + [Flags] + [Obsolete] + public enum ExtVehicleType { + None = 0, + PassengerCar = 1, + Bus = 1 << 1, + Taxi = 1 << 2, + CargoTruck = 1 << 3, + Service = 1 << 4, + Emergency = 1 << 5, + PassengerTrain = 1 << 6, + CargoTrain = 1 << 7, + Tram = 1 << 8, + Bicycle = 1 << 9, + Pedestrian = 1 << 10, + PassengerShip = 1 << 11, + CargoShip = 1 << 12, + PassengerPlane = 1 << 13, + Helicopter = 1 << 14, + CableCar = 1 << 15, + PassengerFerry = 1 << 16, + PassengerBlimp = 1 << 17, + CargoPlane = 1 << 18, + Plane = PassengerPlane | CargoPlane, + Ship = PassengerShip | CargoShip, + CargoVehicle = CargoTruck | CargoTrain | CargoShip | CargoPlane, + PublicTransport = Bus | Taxi | Tram | PassengerTrain, + RoadPublicTransport = Bus | Taxi, + RoadVehicle = PassengerCar | Bus | Taxi | CargoTruck | Service | Emergency, + RailVehicle = PassengerTrain | CargoTrain, + NonTransportRoadVehicle = RoadVehicle & ~PublicTransport, + Ferry = PassengerFerry, + Blimp = PassengerBlimp + } + + public static class LegacyExtVehicleType { + public static API.Traffic.Enums.ExtVehicleType ToNew(ExtVehicleType old) { + return (API.Traffic.Enums.ExtVehicleType)(int)old; + } + + public static ExtVehicleType ToOld(API.Traffic.Enums.ExtVehicleType new_) { + return (ExtVehicleType)(int)new_; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Traffic/Impl/SegmentEnd.cs b/TLM/TLM/Traffic/Impl/SegmentEnd.cs index b0fc8fc7c..64ecf6ab7 100644 --- a/TLM/TLM/Traffic/Impl/SegmentEnd.cs +++ b/TLM/TLM/Traffic/Impl/SegmentEnd.cs @@ -26,6 +26,9 @@ /// (having custom traffic lights or priority signs). /// namespace TrafficManager.Traffic.Impl { + using API.Traffic.Data; + using API.Traffic.Enums; + [Obsolete("should be removed when implementing issue #240")] public class SegmentEnd : SegmentEndId, ISegmentEnd { // TODO convert to struct diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegment.cs b/TLM/TLM/TrafficLight/Impl/CustomSegment.cs index 6b9cc5dee..8883a1e04 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegment.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegment.cs @@ -2,7 +2,9 @@ using TrafficManager.Geometry; namespace TrafficManager.TrafficLight.Impl { - class CustomSegment { + using API.TrafficLight; + + class CustomSegment { public ICustomSegmentLights StartNodeLights; public ICustomSegmentLights EndNodeLights; diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs b/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs index c9a4b3fee..1175cf767 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs @@ -14,6 +14,8 @@ using TrafficManager.Traffic.Data; namespace TrafficManager.TrafficLight.Impl { + using API.Traffic.Enums; + /// /// Represents the traffic light (left, forward, right) at a specific segment end /// diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs b/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs index bea223aa6..f0088360c 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs @@ -1,681 +1,680 @@ #define DEBUGGETx -using System; -using System.Collections.Generic; -using ColossalFramework; -using TrafficManager.Geometry; -using UnityEngine; -using TrafficManager.Custom.AI; -using TrafficManager.Traffic; -using TrafficManager.Manager; -using System.Linq; -using TrafficManager.Util; -using CSUtil.Commons; -using TrafficManager.State; -using TrafficManager.Geometry.Impl; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - namespace TrafficManager.TrafficLight.Impl { - /// - /// Represents the set of custom traffic lights located at a node - /// - public class CustomSegmentLights : SegmentEndId, ICustomSegmentLights { - //private static readonly ExtVehicleType[] SINGLE_LANE_VEHICLETYPES = new ExtVehicleType[] { ExtVehicleType.Tram, ExtVehicleType.Service, ExtVehicleType.CargoTruck, ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service, ExtVehicleType.RailVehicle }; - public const ExtVehicleType DEFAULT_MAIN_VEHICLETYPE = ExtVehicleType.None; - - [Obsolete] - public ushort NodeId { - get { - return Constants.ServiceFactory.NetService.GetSegmentNodeId(SegmentId, StartNode); - } - } - - public uint LastChangeFrame; - - public bool InvalidPedestrianLight { get; set; } = false; // TODO improve & remove - - public IDictionary CustomLights { - get; private set; - } = new TinyDictionary(); - - public LinkedList VehicleTypes { // TODO replace collection - get; private set; - } = new LinkedList(); - - public ExtVehicleType?[] VehicleTypeByLaneIndex { - get; private set; - } = new ExtVehicleType?[0]; - - /// - /// Vehicles types that have their own traffic light - /// - public ExtVehicleType SeparateVehicleTypes { - get; private set; - } = ExtVehicleType.None; - - public RoadBaseAI.TrafficLightState AutoPedestrianLightState { get; set; } = RoadBaseAI.TrafficLightState.Green; // TODO set should be private - - public RoadBaseAI.TrafficLightState? PedestrianLightState { - get { - if (InvalidPedestrianLight || InternalPedestrianLightState == null) - return RoadBaseAI.TrafficLightState.Green; // no pedestrian crossing at this point - - if (ManualPedestrianMode && InternalPedestrianLightState != null) - return (RoadBaseAI.TrafficLightState)InternalPedestrianLightState; - else { - return AutoPedestrianLightState; - } - } - set { - if (InternalPedestrianLightState == null) { + using System; + using System.Collections.Generic; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Geometry.Impl; + using Manager; + using State; + using Traffic.Data; + using Util; + + /// + /// Represents the set of custom traffic lights located at a node + /// + public class CustomSegmentLights : SegmentEndId, ICustomSegmentLights { + // private static readonly ExtVehicleType[] SINGLE_LANE_VEHICLETYPES + // = new ExtVehicleType[] { ExtVehicleType.Tram, ExtVehicleType.Service, + // ExtVehicleType.CargoTruck, ExtVehicleType.RoadPublicTransport + // | ExtVehicleType.Service, ExtVehicleType.RailVehicle }; + public const ExtVehicleType DEFAULT_MAIN_VEHICLETYPE = ExtVehicleType.None; + + [Obsolete] + public ushort NodeId { + get { + return Constants.ServiceFactory.NetService.GetSegmentNodeId(SegmentId, StartNode); + } + } + + public uint LastChangeFrame; + + public bool InvalidPedestrianLight { get; set; } = false; // TODO improve & remove + + public IDictionary CustomLights { + get; private set; + } = new TinyDictionary(); + + public LinkedList VehicleTypes { // TODO replace collection + get; private set; + } = new LinkedList(); + + public ExtVehicleType?[] VehicleTypeByLaneIndex { + get; private set; + } = new ExtVehicleType?[0]; + + /// + /// Vehicles types that have their own traffic light + /// + public ExtVehicleType SeparateVehicleTypes { + get; private set; + } = ExtVehicleType.None; + + public RoadBaseAI.TrafficLightState AutoPedestrianLightState { get; set; } = RoadBaseAI.TrafficLightState.Green; // TODO set should be private + + public RoadBaseAI.TrafficLightState? PedestrianLightState { + get { + if (InvalidPedestrianLight || InternalPedestrianLightState == null) + return RoadBaseAI.TrafficLightState.Green; // no pedestrian crossing at this point + + if (ManualPedestrianMode && InternalPedestrianLightState != null) + return (RoadBaseAI.TrafficLightState)InternalPedestrianLightState; + else { + return AutoPedestrianLightState; + } + } + set { + if (InternalPedestrianLightState == null) { #if DEBUGHK - Log._Debug($"CustomSegmentLights: Refusing to change pedestrian light at segment {SegmentId}"); + Log._Debug($"CustomSegmentLights: Refusing to change pedestrian light at segment {SegmentId}"); #endif - return; - } - //Log._Debug($"CustomSegmentLights: Setting pedestrian light at segment {segmentId}"); - InternalPedestrianLightState = value; - } - } - - public bool ManualPedestrianMode { - get { return manualPedestrianMode; } - set { - if (!manualPedestrianMode && value) { - PedestrianLightState = AutoPedestrianLightState; - } - manualPedestrianMode = value; - } - } - - private bool manualPedestrianMode = false; - - public RoadBaseAI.TrafficLightState? InternalPedestrianLightState { get; private set; } = null; - private ExtVehicleType mainVehicleType = ExtVehicleType.None; - protected ICustomSegmentLight MainSegmentLight { - get { - ICustomSegmentLight res = null; - CustomLights.TryGetValue(mainVehicleType, out res); - return res; - } - } - - public ICustomSegmentLightsManager LightsManager { - get { - return lightsManager; - } - set { - lightsManager = value; - OnChange(); - } - } - private ICustomSegmentLightsManager lightsManager; - - public override string ToString() { - return $"[CustomSegmentLights {base.ToString()} @ node {NodeId}\n" + - "\t" + $"LastChangeFrame: {LastChangeFrame}\n" + - "\t" + $"InvalidPedestrianLight: {InvalidPedestrianLight}\n" + - "\t" + $"CustomLights: {CustomLights}\n" + - "\t" + $"VehicleTypes: {VehicleTypes.CollectionToString()}\n" + - "\t" + $"VehicleTypeByLaneIndex: {VehicleTypeByLaneIndex.ArrayToString()}\n" + - "\t" + $"SeparateVehicleTypes: {SeparateVehicleTypes}\n" + - "\t" + $"AutoPedestrianLightState: {AutoPedestrianLightState}\n" + - "\t" + $"PedestrianLightState: {PedestrianLightState}\n" + - "\t" + $"ManualPedestrianMode: {ManualPedestrianMode}\n" + - "\t" + $"manualPedestrianMode: {manualPedestrianMode}\n" + - "\t" + $"InternalPedestrianLightState: {InternalPedestrianLightState}\n" + - "\t" + $"MainSegmentLight: {MainSegmentLight}\n" + - "CustomSegmentLights]"; - } - - public bool Relocate(ushort segmentId, bool startNode, ICustomSegmentLightsManager lightsManager) { - if (Relocate(segmentId, startNode)) { - this.lightsManager = lightsManager; - Housekeeping(true, true); - return true; - } - return false; - } - - [Obsolete] - protected CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort nodeId, ushort segmentId, bool calculateAutoPedLight) - : this(lightsManager, segmentId, nodeId == Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, true), calculateAutoPedLight) { - - } - - public CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort segmentId, bool startNode, bool calculateAutoPedLight) : this(lightsManager, segmentId, startNode, calculateAutoPedLight, true) { - - } - - public CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort segmentId, bool startNode, bool calculateAutoPedLight, bool performHousekeeping) : base(segmentId, startNode) { - this.lightsManager = lightsManager; - if (performHousekeeping) { - Housekeeping(false, calculateAutoPedLight); - } - } - - public bool IsAnyGreen() { - foreach (KeyValuePair e in CustomLights) { - if (e.Value.IsAnyGreen()) - return true; - } - return false; - } - - public bool IsAnyInTransition() { - foreach (KeyValuePair e in CustomLights) { - if (e.Value.IsAnyInTransition()) - return true; - } - return false; - } - - public bool IsAnyLeftGreen() { - foreach (KeyValuePair e in CustomLights) { - if (e.Value.IsLeftGreen()) - return true; - } - return false; - } - - public bool IsAnyMainGreen() { - foreach (KeyValuePair e in CustomLights) { - if (e.Value.IsMainGreen()) - return true; - } - return false; - } - - public bool IsAnyRightGreen() { - foreach (KeyValuePair e in CustomLights) { - if (e.Value.IsRightGreen()) - return true; - } - return false; - } - - public bool IsAllLeftRed() { - foreach (KeyValuePair e in CustomLights) { - if (!e.Value.IsLeftRed()) - return false; - } - return true; - } - - public bool IsAllMainRed() { - foreach (KeyValuePair e in CustomLights) { - if (!e.Value.IsMainRed()) - return false; - } - return true; - } - - public bool IsAllRightRed() { - foreach (KeyValuePair e in CustomLights) { - if (!e.Value.IsRightRed()) - return false; - } - return true; - } - - public void UpdateVisuals() { - if (MainSegmentLight == null) - return; - - MainSegmentLight.UpdateVisuals(); - } - - public object Clone() { - return Clone(LightsManager, true); - } - - public ICustomSegmentLights Clone(ICustomSegmentLightsManager newLightsManager, bool performHousekeeping=true) { - CustomSegmentLights clone = new CustomSegmentLights(newLightsManager != null ? newLightsManager : LightsManager, SegmentId, StartNode, false, false); - foreach (KeyValuePair e in CustomLights) { - clone.CustomLights.Add(e.Key, (ICustomSegmentLight)e.Value.Clone()); - } - clone.InternalPedestrianLightState = InternalPedestrianLightState; - clone.manualPedestrianMode = manualPedestrianMode; - clone.VehicleTypes = new LinkedList(VehicleTypes); - clone.LastChangeFrame = LastChangeFrame; - clone.mainVehicleType = mainVehicleType; - clone.AutoPedestrianLightState = AutoPedestrianLightState; - if (performHousekeeping) { - clone.Housekeeping(false, false); - } - return clone; - } - - public ICustomSegmentLight GetCustomLight(byte laneIndex) { - if (laneIndex >= VehicleTypeByLaneIndex.Length) { + return; + } + //Log._Debug($"CustomSegmentLights: Setting pedestrian light at segment {segmentId}"); + InternalPedestrianLightState = value; + } + } + + public bool ManualPedestrianMode { + get { return manualPedestrianMode; } + set { + if (!manualPedestrianMode && value) { + PedestrianLightState = AutoPedestrianLightState; + } + manualPedestrianMode = value; + } + } + + private bool manualPedestrianMode = false; + + public RoadBaseAI.TrafficLightState? InternalPedestrianLightState { get; private set; } = null; + private ExtVehicleType mainVehicleType = ExtVehicleType.None; + protected ICustomSegmentLight MainSegmentLight { + get { + ICustomSegmentLight res = null; + CustomLights.TryGetValue(mainVehicleType, out res); + return res; + } + } + + public ICustomSegmentLightsManager LightsManager { + get { + return lightsManager; + } + set { + lightsManager = value; + OnChange(); + } + } + private ICustomSegmentLightsManager lightsManager; + + public override string ToString() { + return $"[CustomSegmentLights {base.ToString()} @ node {NodeId}\n" + + "\t" + $"LastChangeFrame: {LastChangeFrame}\n" + + "\t" + $"InvalidPedestrianLight: {InvalidPedestrianLight}\n" + + "\t" + $"CustomLights: {CustomLights}\n" + + "\t" + $"VehicleTypes: {VehicleTypes.CollectionToString()}\n" + + "\t" + $"VehicleTypeByLaneIndex: {VehicleTypeByLaneIndex.ArrayToString()}\n" + + "\t" + $"SeparateVehicleTypes: {SeparateVehicleTypes}\n" + + "\t" + $"AutoPedestrianLightState: {AutoPedestrianLightState}\n" + + "\t" + $"PedestrianLightState: {PedestrianLightState}\n" + + "\t" + $"ManualPedestrianMode: {ManualPedestrianMode}\n" + + "\t" + $"manualPedestrianMode: {manualPedestrianMode}\n" + + "\t" + $"InternalPedestrianLightState: {InternalPedestrianLightState}\n" + + "\t" + $"MainSegmentLight: {MainSegmentLight}\n" + + "CustomSegmentLights]"; + } + + public bool Relocate(ushort segmentId, bool startNode, ICustomSegmentLightsManager lightsManager) { + if (Relocate(segmentId, startNode)) { + this.lightsManager = lightsManager; + Housekeeping(true, true); + return true; + } + return false; + } + + [Obsolete] + protected CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort nodeId, ushort segmentId, bool calculateAutoPedLight) + : this(lightsManager, segmentId, nodeId == Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, true), calculateAutoPedLight) { + + } + + public CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort segmentId, bool startNode, bool calculateAutoPedLight) : this(lightsManager, segmentId, startNode, calculateAutoPedLight, true) { + + } + + public CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort segmentId, bool startNode, bool calculateAutoPedLight, bool performHousekeeping) : base(segmentId, startNode) { + this.lightsManager = lightsManager; + if (performHousekeeping) { + Housekeeping(false, calculateAutoPedLight); + } + } + + public bool IsAnyGreen() { + foreach (KeyValuePair e in CustomLights) { + if (e.Value.IsAnyGreen()) + return true; + } + return false; + } + + public bool IsAnyInTransition() { + foreach (KeyValuePair e in CustomLights) { + if (e.Value.IsAnyInTransition()) + return true; + } + return false; + } + + public bool IsAnyLeftGreen() { + foreach (KeyValuePair e in CustomLights) { + if (e.Value.IsLeftGreen()) + return true; + } + return false; + } + + public bool IsAnyMainGreen() { + foreach (KeyValuePair e in CustomLights) { + if (e.Value.IsMainGreen()) + return true; + } + return false; + } + + public bool IsAnyRightGreen() { + foreach (KeyValuePair e in CustomLights) { + if (e.Value.IsRightGreen()) + return true; + } + return false; + } + + public bool IsAllLeftRed() { + foreach (KeyValuePair e in CustomLights) { + if (!e.Value.IsLeftRed()) + return false; + } + return true; + } + + public bool IsAllMainRed() { + foreach (KeyValuePair e in CustomLights) { + if (!e.Value.IsMainRed()) + return false; + } + return true; + } + + public bool IsAllRightRed() { + foreach (KeyValuePair e in CustomLights) { + if (!e.Value.IsRightRed()) + return false; + } + return true; + } + + public void UpdateVisuals() { + if (MainSegmentLight == null) + return; + + MainSegmentLight.UpdateVisuals(); + } + + public object Clone() { + return Clone(LightsManager, true); + } + + public ICustomSegmentLights Clone(ICustomSegmentLightsManager newLightsManager, bool performHousekeeping=true) { + CustomSegmentLights clone = new CustomSegmentLights(newLightsManager != null ? newLightsManager : LightsManager, SegmentId, StartNode, false, false); + foreach (KeyValuePair e in CustomLights) { + clone.CustomLights.Add(e.Key, (ICustomSegmentLight)e.Value.Clone()); + } + clone.InternalPedestrianLightState = InternalPedestrianLightState; + clone.manualPedestrianMode = manualPedestrianMode; + clone.VehicleTypes = new LinkedList(VehicleTypes); + clone.LastChangeFrame = LastChangeFrame; + clone.mainVehicleType = mainVehicleType; + clone.AutoPedestrianLightState = AutoPedestrianLightState; + if (performHousekeeping) { + clone.Housekeeping(false, false); + } + return clone; + } + + public ICustomSegmentLight GetCustomLight(byte laneIndex) { + if (laneIndex >= VehicleTypeByLaneIndex.Length) { #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): No vehicle type found for lane index"); #endif - return MainSegmentLight; - } + return MainSegmentLight; + } - ExtVehicleType? vehicleType = VehicleTypeByLaneIndex[laneIndex]; + ExtVehicleType? vehicleType = VehicleTypeByLaneIndex[laneIndex]; - if (vehicleType == null) { + if (vehicleType == null) { #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): No vehicle type found for lane index: lane is invalid"); #endif - return MainSegmentLight; - } + return MainSegmentLight; + } #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): Vehicle type is {vehicleType}"); #endif - ICustomSegmentLight light; - if (!CustomLights.TryGetValue((ExtVehicleType)vehicleType, out light)) { + ICustomSegmentLight light; + if (!CustomLights.TryGetValue((ExtVehicleType)vehicleType, out light)) { #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): No custom light found for vehicle type {vehicleType}"); #endif - return MainSegmentLight; - } + return MainSegmentLight; + } #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): Returning custom light for vehicle type {vehicleType}"); #endif - return light; - } - - public ICustomSegmentLight GetCustomLight(ExtVehicleType vehicleType) { - ICustomSegmentLight ret = null; - if (!CustomLights.TryGetValue(vehicleType, out ret)) { - ret = MainSegmentLight; - } - - return ret; - - /*if (vehicleType != ExtVehicleType.None) - Log._Debug($"No traffic light for vehicle type {vehicleType} defined at segment {segmentId}, node {nodeId}.");*/ - } - - public void MakeRed() { - foreach (KeyValuePair e in CustomLights) { - e.Value.MakeRed(); - } - } - - public void MakeRedOrGreen() { - foreach (KeyValuePair e in CustomLights) { - e.Value.MakeRedOrGreen(); - } - } - - public void SetLights(RoadBaseAI.TrafficLightState lightState) { - foreach (KeyValuePair e in CustomLights) { - e.Value.SetStates(lightState, lightState, lightState, false); - } - - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - CalculateAutoPedestrianLightState(ref node); - return true; - }); - } - - public void SetLights(ICustomSegmentLights otherLights) { - foreach (KeyValuePair e in otherLights.CustomLights) { - ICustomSegmentLight ourLight = null; - if (!CustomLights.TryGetValue(e.Key, out ourLight)) { - continue; - } - - ourLight.SetStates(e.Value.LightMain, e.Value.LightLeft, e.Value.LightRight, false); - //ourLight.LightPedestrian = e.Value.LightPedestrian; - } - InternalPedestrianLightState = otherLights.InternalPedestrianLightState; - manualPedestrianMode = otherLights.ManualPedestrianMode; - AutoPedestrianLightState = otherLights.AutoPedestrianLightState; - } - - public void ChangeLightPedestrian() { - if (PedestrianLightState != null) { - var invertedLight = PedestrianLightState == RoadBaseAI.TrafficLightState.Green - ? RoadBaseAI.TrafficLightState.Red - : RoadBaseAI.TrafficLightState.Green; - - PedestrianLightState = invertedLight; - UpdateVisuals(); - } - } - - private static uint getCurrentFrame() { - return Singleton.instance.m_currentFrameIndex >> 6; - } - - public uint LastChange() { - return getCurrentFrame() - LastChangeFrame; - } - - public void OnChange(bool calculateAutoPedLight=true) { - LastChangeFrame = getCurrentFrame(); - - if (calculateAutoPedLight) { - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - CalculateAutoPedestrianLightState(ref node); - return true; - }); - } - } - - public void CalculateAutoPedestrianLightState(ref NetNode node, bool propagate=true) { + return light; + } + + public ICustomSegmentLight GetCustomLight(ExtVehicleType vehicleType) { + ICustomSegmentLight ret = null; + if (!CustomLights.TryGetValue(vehicleType, out ret)) { + ret = MainSegmentLight; + } + + return ret; + + /*if (vehicleType != ExtVehicleType.None) + Log._Debug($"No traffic light for vehicle type {vehicleType} defined at segment {segmentId}, node {nodeId}.");*/ + } + + public void MakeRed() { + foreach (KeyValuePair e in CustomLights) { + e.Value.MakeRed(); + } + } + + public void MakeRedOrGreen() { + foreach (KeyValuePair e in CustomLights) { + e.Value.MakeRedOrGreen(); + } + } + + public void SetLights(RoadBaseAI.TrafficLightState lightState) { + foreach (KeyValuePair e in CustomLights) { + e.Value.SetStates(lightState, lightState, lightState, false); + } + + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + CalculateAutoPedestrianLightState(ref node); + return true; + }); + } + + public void SetLights(ICustomSegmentLights otherLights) { + foreach (KeyValuePair e in otherLights.CustomLights) { + ICustomSegmentLight ourLight = null; + if (!CustomLights.TryGetValue(e.Key, out ourLight)) { + continue; + } + + ourLight.SetStates(e.Value.LightMain, e.Value.LightLeft, e.Value.LightRight, false); + //ourLight.LightPedestrian = e.Value.LightPedestrian; + } + InternalPedestrianLightState = otherLights.InternalPedestrianLightState; + manualPedestrianMode = otherLights.ManualPedestrianMode; + AutoPedestrianLightState = otherLights.AutoPedestrianLightState; + } + + public void ChangeLightPedestrian() { + if (PedestrianLightState != null) { + var invertedLight = PedestrianLightState == RoadBaseAI.TrafficLightState.Green + ? RoadBaseAI.TrafficLightState.Red + : RoadBaseAI.TrafficLightState.Green; + + PedestrianLightState = invertedLight; + UpdateVisuals(); + } + } + + private static uint getCurrentFrame() { + return Singleton.instance.m_currentFrameIndex >> 6; + } + + public uint LastChange() { + return getCurrentFrame() - LastChangeFrame; + } + + public void OnChange(bool calculateAutoPedLight=true) { + LastChangeFrame = getCurrentFrame(); + + if (calculateAutoPedLight) { + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + CalculateAutoPedestrianLightState(ref node); + return true; + }); + } + } + + public void CalculateAutoPedestrianLightState(ref NetNode node, bool propagate=true) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculating pedestrian light state of seg. {SegmentId} @ node {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculating pedestrian light state of seg. {SegmentId} @ node {NodeId}"); #endif - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - ExtSegment seg = segMan.ExtSegments[SegmentId]; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(SegmentId, StartNode)]; - - ushort nodeId = segEnd.nodeId; - if (nodeId != NodeId) { - Log.Warning($"CustomSegmentLights.CalculateAutoPedestrianLightState: Node id mismatch! segment end node is {nodeId} but we are node {NodeId}. segEnd={segEnd} this={this}"); - return; - } - - if (propagate) { - for (int i = 0; i < 8; ++i) { - ushort otherSegmentId = node.GetSegment(i); - if (otherSegmentId == 0 || otherSegmentId == SegmentId) - continue; - - ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); - if (otherLights == null) { + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + ExtSegment seg = segMan.ExtSegments[SegmentId]; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(SegmentId, StartNode)]; + + ushort nodeId = segEnd.nodeId; + if (nodeId != NodeId) { + Log.Warning($"CustomSegmentLights.CalculateAutoPedestrianLightState: Node id mismatch! segment end node is {nodeId} but we are node {NodeId}. segEnd={segEnd} this={this}"); + return; + } + + if (propagate) { + for (int i = 0; i < 8; ++i) { + ushort otherSegmentId = node.GetSegment(i); + if (otherSegmentId == 0 || otherSegmentId == SegmentId) + continue; + + ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); + if (otherLights == null) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (propagate) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (propagate) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); #endif - continue; - } + continue; + } - otherLights.CalculateAutoPedestrianLightState(ref node, false); - } - } + otherLights.CalculateAutoPedestrianLightState(ref node, false); + } + } - if (IsAnyGreen()) { + if (IsAnyGreen()) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Any green at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Any green at seg. {SegmentId} @ {NodeId}"); #endif - AutoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; - return; - } + AutoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; + return; + } #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Querying incoming segments at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Querying incoming segments at seg. {SegmentId} @ {NodeId}"); #endif - ItemClass prevConnectionClass = null; - Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort prevSegId, ref NetSegment segment) { - prevConnectionClass = segment.Info.GetConnectionClass(); - return true; - }); + ItemClass prevConnectionClass = null; + Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort prevSegId, ref NetSegment segment) { + prevConnectionClass = segment.Info.GetConnectionClass(); + return true; + }); - RoadBaseAI.TrafficLightState autoPedestrianLightState = RoadBaseAI.TrafficLightState.Green; - bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; - if (!(segEnd.incoming && seg.oneWay)) { - for (int i = 0; i < 8; ++i) { - ushort otherSegmentId = node.GetSegment(i); - if (otherSegmentId == 0 || otherSegmentId == SegmentId) - continue; + RoadBaseAI.TrafficLightState autoPedestrianLightState = RoadBaseAI.TrafficLightState.Green; + bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; + if (!(segEnd.incoming && seg.oneWay)) { + for (int i = 0; i < 8; ++i) { + ushort otherSegmentId = node.GetSegment(i); + if (otherSegmentId == 0 || otherSegmentId == SegmentId) + continue; - //ExtSegment otherSeg = segMan.ExtSegments[otherSegmentId]; + //ExtSegment otherSeg = segMan.ExtSegments[otherSegmentId]; - if (!segEndMan.ExtSegmentEnds[segEndMan.GetIndex(otherSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, NodeId))].incoming) { - continue; - } + if (!segEndMan.ExtSegmentEnds[segEndMan.GetIndex(otherSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, NodeId))].incoming) { + continue; + } #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Checking incoming straight segment {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Checking incoming straight segment {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif - ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); - if (otherLights == null) { + ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); + if (otherLights == null) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (straight) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (straight) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); #endif - continue; - } + continue; + } - ItemClass nextConnectionClass = null; - Constants.ServiceFactory.NetService.ProcessSegment(otherSegmentId, delegate (ushort otherSegId, ref NetSegment segment) { - nextConnectionClass = segment.Info.GetConnectionClass(); - return true; - }); + ItemClass nextConnectionClass = null; + Constants.ServiceFactory.NetService.ProcessSegment(otherSegmentId, delegate (ushort otherSegId, ref NetSegment segment) { + nextConnectionClass = segment.Info.GetConnectionClass(); + return true; + }); - if (nextConnectionClass.m_service != prevConnectionClass.m_service) { + if (nextConnectionClass.m_service != prevConnectionClass.m_service) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Other (straight) segment {otherSegmentId} @ {NodeId} has different connection service than segment {SegmentId} ({nextConnectionClass.m_service} vs. {prevConnectionClass.m_service}). Ignoring traffic light state."); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Other (straight) segment {otherSegmentId} @ {NodeId} has different connection service than segment {SegmentId} ({nextConnectionClass.m_service} vs. {prevConnectionClass.m_service}). Ignoring traffic light state."); #endif - continue; - } + continue; + } - ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); - if (dir == ArrowDirection.Forward) { - if (!otherLights.IsAllMainRed()) { + ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); + if (dir == ArrowDirection.Forward) { + if (!otherLights.IsAllMainRed()) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all main red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all main red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif - autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; - break; - } - } else if ((dir == ArrowDirection.Left && lhd) || (dir == ArrowDirection.Right && !lhd)) { - if ((lhd && !otherLights.IsAllRightRed()) || (!lhd && !otherLights.IsAllLeftRed())) { + autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; + break; + } + } else if ((dir == ArrowDirection.Left && lhd) || (dir == ArrowDirection.Right && !lhd)) { + if ((lhd && !otherLights.IsAllRightRed()) || (!lhd && !otherLights.IsAllLeftRed())) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all left red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all left red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif - autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; - break; - } - } - } - } - - AutoPedestrianLightState = autoPedestrianLightState; + autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; + break; + } + } + } + } + + AutoPedestrianLightState = autoPedestrianLightState; #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculated AutoPedestrianLightState for segment {SegmentId} @ {NodeId}: {AutoPedestrianLightState}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculated AutoPedestrianLightState for segment {SegmentId} @ {NodeId}: {AutoPedestrianLightState}"); #endif - } + } - // TODO improve & remove - public void Housekeeping(bool mayDelete, bool calculateAutoPedLight) { + // TODO improve & remove + public void Housekeeping(bool mayDelete, bool calculateAutoPedLight) { #if DEBUGHK - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif - // we intentionally never delete vehicle types (because we may want to retain traffic light states if a segment is upgraded or replaced) + // we intentionally never delete vehicle types (because we may want to retain traffic light states if a segment is upgraded or replaced) - ICustomSegmentLight mainLight = MainSegmentLight; - ushort nodeId = NodeId; - HashSet setupLights = new HashSet(); - IDictionary allAllowedTypes = Constants.ManagerFactory.VehicleRestrictionsManager.GetAllowedVehicleTypesAsDict(SegmentId, nodeId, VehicleRestrictionsMode.Restricted); // TODO improve - ExtVehicleType allAllowedMask = Constants.ManagerFactory.VehicleRestrictionsManager.GetAllowedVehicleTypes(SegmentId, nodeId, VehicleRestrictionsMode.Restricted); - SeparateVehicleTypes = ExtVehicleType.None; + ICustomSegmentLight mainLight = MainSegmentLight; + ushort nodeId = NodeId; + HashSet setupLights = new HashSet(); + IDictionary allAllowedTypes = Constants.ManagerFactory.VehicleRestrictionsManager.GetAllowedVehicleTypesAsDict(SegmentId, nodeId, VehicleRestrictionsMode.Restricted); // TODO improve + ExtVehicleType allAllowedMask = Constants.ManagerFactory.VehicleRestrictionsManager.GetAllowedVehicleTypes(SegmentId, nodeId, VehicleRestrictionsMode.Restricted); + SeparateVehicleTypes = ExtVehicleType.None; #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping started @ seg. {SegmentId}, node {nodeId}, allAllowedTypes={allAllowedTypes.DictionaryToString()}, allAllowedMask={allAllowedMask}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping started @ seg. {SegmentId}, node {nodeId}, allAllowedTypes={allAllowedTypes.DictionaryToString()}, allAllowedMask={allAllowedMask}"); #endif - //bool addPedestrianLight = false; - uint separateLanes = 0; - int defaultLanes = 0; - NetInfo segmentInfo = null; - Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort segId, ref NetSegment segment) { - VehicleTypeByLaneIndex = new ExtVehicleType?[segment.Info.m_lanes.Length]; - segmentInfo = segment.Info; - return true; - }); - HashSet laneIndicesWithoutSeparateLights = new HashSet(allAllowedTypes.Keys); // TODO improve - - // check if separate traffic lights are required - bool separateLightsRequired = false; - foreach (KeyValuePair e in allAllowedTypes) { - if (e.Value != allAllowedMask) { - separateLightsRequired = true; - break; - } - } - - // set up vehicle-separated traffic lights - if (separateLightsRequired) { - foreach (KeyValuePair e in allAllowedTypes) { - byte laneIndex = e.Key; - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - ExtVehicleType allowedTypes = e.Value; - ExtVehicleType defaultMask = Constants.ManagerFactory.VehicleRestrictionsManager.GetDefaultAllowedVehicleTypes(SegmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Unrestricted); + //bool addPedestrianLight = false; + uint separateLanes = 0; + int defaultLanes = 0; + NetInfo segmentInfo = null; + Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort segId, ref NetSegment segment) { + VehicleTypeByLaneIndex = new ExtVehicleType?[segment.Info.m_lanes.Length]; + segmentInfo = segment.Info; + return true; + }); + HashSet laneIndicesWithoutSeparateLights = new HashSet(allAllowedTypes.Keys); // TODO improve + + // check if separate traffic lights are required + bool separateLightsRequired = false; + foreach (KeyValuePair e in allAllowedTypes) { + if (e.Value != allAllowedMask) { + separateLightsRequired = true; + break; + } + } + + // set up vehicle-separated traffic lights + if (separateLightsRequired) { + foreach (KeyValuePair e in allAllowedTypes) { + byte laneIndex = e.Key; + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + ExtVehicleType allowedTypes = e.Value; + ExtVehicleType defaultMask = Constants.ManagerFactory.VehicleRestrictionsManager.GetDefaultAllowedVehicleTypes(SegmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Unrestricted); #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Processing lane {laneIndex} with allowedTypes={allowedTypes}, defaultMask={defaultMask}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Processing lane {laneIndex} with allowedTypes={allowedTypes}, defaultMask={defaultMask}"); #endif - if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.Car && allowedTypes == defaultMask) { + if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.Car && allowedTypes == defaultMask) { #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Allowed types equal default mask. Ignoring lane."); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Allowed types equal default mask. Ignoring lane."); #endif - // no vehicle restrictions applied, generic lights are handled further below - ++defaultLanes; - continue; - } + // no vehicle restrictions applied, generic lights are handled further below + ++defaultLanes; + continue; + } - ExtVehicleType mask = allowedTypes & ~ExtVehicleType.Emergency; + ExtVehicleType mask = allowedTypes & ~ExtVehicleType.Emergency; #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Trying to add {mask} light"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Trying to add {mask} light"); #endif - ICustomSegmentLight segmentLight; - if (!CustomLights.TryGetValue(mask, out segmentLight)) { - // add a new light - segmentLight = new CustomSegmentLight(this, RoadBaseAI.TrafficLightState.Red); - if (mainLight != null) { - segmentLight.CurrentMode = mainLight.CurrentMode; - segmentLight.SetStates(mainLight.LightMain, mainLight.LightLeft, mainLight.LightRight, false); - } + ICustomSegmentLight segmentLight; + if (!CustomLights.TryGetValue(mask, out segmentLight)) { + // add a new light + segmentLight = new CustomSegmentLight(this, RoadBaseAI.TrafficLightState.Red); + if (mainLight != null) { + segmentLight.CurrentMode = mainLight.CurrentMode; + segmentLight.SetStates(mainLight.LightMain, mainLight.LightLeft, mainLight.LightRight, false); + } #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Light for mask {mask} does not exist. Created new light: {segmentLight} (mainLight: {mainLight})"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Light for mask {mask} does not exist. Created new light: {segmentLight} (mainLight: {mainLight})"); #endif - CustomLights.Add(mask, segmentLight); - VehicleTypes.AddFirst(mask); - } + CustomLights.Add(mask, segmentLight); + VehicleTypes.AddFirst(mask); + } - mainVehicleType = mask; - VehicleTypeByLaneIndex[laneIndex] = mask; - laneIndicesWithoutSeparateLights.Remove(laneIndex); - ++separateLanes; - //addPedestrianLight = true; - setupLights.Add(mask); - SeparateVehicleTypes |= mask; + mainVehicleType = mask; + VehicleTypeByLaneIndex[laneIndex] = mask; + laneIndicesWithoutSeparateLights.Remove(laneIndex); + ++separateLanes; + //addPedestrianLight = true; + setupLights.Add(mask); + SeparateVehicleTypes |= mask; #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Finished processing lane {laneIndex}: mainVehicleType={mainVehicleType}, VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()}, laneIndicesWithoutSeparateLights={laneIndicesWithoutSeparateLights.CollectionToString()}, numLights={separateLanes}, SeparateVehicleTypes={SeparateVehicleTypes}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Finished processing lane {laneIndex}: mainVehicleType={mainVehicleType}, VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()}, laneIndicesWithoutSeparateLights={laneIndicesWithoutSeparateLights.CollectionToString()}, numLights={separateLanes}, SeparateVehicleTypes={SeparateVehicleTypes}"); #endif - } - } + } + } - if (separateLanes == 0 || defaultLanes > 0) { + if (separateLanes == 0 || defaultLanes > 0) { #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Adding default main vehicle light: {DEFAULT_MAIN_VEHICLETYPE}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Adding default main vehicle light: {DEFAULT_MAIN_VEHICLETYPE}"); #endif - // generic traffic lights - ICustomSegmentLight defaultSegmentLight; - if (!CustomLights.TryGetValue(DEFAULT_MAIN_VEHICLETYPE, out defaultSegmentLight)) { - defaultSegmentLight = new CustomSegmentLight(this, RoadBaseAI.TrafficLightState.Red); - if (mainLight != null) { - defaultSegmentLight.CurrentMode = mainLight.CurrentMode; - defaultSegmentLight.SetStates(mainLight.LightMain, mainLight.LightLeft, mainLight.LightRight, false); - } - CustomLights.Add(DEFAULT_MAIN_VEHICLETYPE, defaultSegmentLight); - VehicleTypes.AddFirst(DEFAULT_MAIN_VEHICLETYPE); - } - mainVehicleType = DEFAULT_MAIN_VEHICLETYPE; - setupLights.Add(DEFAULT_MAIN_VEHICLETYPE); - - foreach (byte laneIndex in laneIndicesWithoutSeparateLights) { - VehicleTypeByLaneIndex[laneIndex] = ExtVehicleType.None; - } + // generic traffic lights + ICustomSegmentLight defaultSegmentLight; + if (!CustomLights.TryGetValue(DEFAULT_MAIN_VEHICLETYPE, out defaultSegmentLight)) { + defaultSegmentLight = new CustomSegmentLight(this, RoadBaseAI.TrafficLightState.Red); + if (mainLight != null) { + defaultSegmentLight.CurrentMode = mainLight.CurrentMode; + defaultSegmentLight.SetStates(mainLight.LightMain, mainLight.LightLeft, mainLight.LightRight, false); + } + CustomLights.Add(DEFAULT_MAIN_VEHICLETYPE, defaultSegmentLight); + VehicleTypes.AddFirst(DEFAULT_MAIN_VEHICLETYPE); + } + mainVehicleType = DEFAULT_MAIN_VEHICLETYPE; + setupLights.Add(DEFAULT_MAIN_VEHICLETYPE); + + foreach (byte laneIndex in laneIndicesWithoutSeparateLights) { + VehicleTypeByLaneIndex[laneIndex] = ExtVehicleType.None; + } #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Added default main vehicle light: {defaultSegmentLight}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Added default main vehicle light: {defaultSegmentLight}"); #endif - //addPedestrianLight = true; - } else { - //addPedestrianLight = allAllowedMask == ExtVehicleType.None || (allAllowedMask & ~ExtVehicleType.RailVehicle) != ExtVehicleType.None; - } + //addPedestrianLight = true; + } else { + //addPedestrianLight = allAllowedMask == ExtVehicleType.None || (allAllowedMask & ~ExtVehicleType.RailVehicle) != ExtVehicleType.None; + } #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Created all necessary lights. VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()}, CustomLights={CustomLights.DictionaryToString()}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Created all necessary lights. VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()}, CustomLights={CustomLights.DictionaryToString()}"); #endif - if (mayDelete) { - // delete traffic lights for non-existing vehicle-separated configurations - HashSet vehicleTypesToDelete = new HashSet(); - foreach (KeyValuePair e in CustomLights) { - /*if (e.Key == DEFAULT_MAIN_VEHICLETYPE) { - continue; - }*/ - if (!setupLights.Contains(e.Key)) { - vehicleTypesToDelete.Add(e.Key); - } - } + if (mayDelete) { + // delete traffic lights for non-existing vehicle-separated configurations + HashSet vehicleTypesToDelete = new HashSet(); + foreach (KeyValuePair e in CustomLights) { + /*if (e.Key == DEFAULT_MAIN_VEHICLETYPE) { + continue; + }*/ + if (!setupLights.Contains(e.Key)) { + vehicleTypesToDelete.Add(e.Key); + } + } #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Going to delete unnecessary lights now: vehicleTypesToDelete={vehicleTypesToDelete.CollectionToString()}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Going to delete unnecessary lights now: vehicleTypesToDelete={vehicleTypesToDelete.CollectionToString()}"); #endif - foreach (ExtVehicleType vehicleType in vehicleTypesToDelete) { - CustomLights.Remove(vehicleType); - VehicleTypes.Remove(vehicleType); - } - } + foreach (ExtVehicleType vehicleType in vehicleTypesToDelete) { + CustomLights.Remove(vehicleType); + VehicleTypes.Remove(vehicleType); + } + } - if (CustomLights.ContainsKey(DEFAULT_MAIN_VEHICLETYPE) && VehicleTypes.First.Value != DEFAULT_MAIN_VEHICLETYPE) { - VehicleTypes.Remove(DEFAULT_MAIN_VEHICLETYPE); - VehicleTypes.AddFirst(DEFAULT_MAIN_VEHICLETYPE); - } + if (CustomLights.ContainsKey(DEFAULT_MAIN_VEHICLETYPE) && VehicleTypes.First.Value != DEFAULT_MAIN_VEHICLETYPE) { + VehicleTypes.Remove(DEFAULT_MAIN_VEHICLETYPE); + VehicleTypes.AddFirst(DEFAULT_MAIN_VEHICLETYPE); + } - //if (addPedestrianLight) { + //if (addPedestrianLight) { #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: adding pedestrian light"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: adding pedestrian light"); #endif - if (InternalPedestrianLightState == null) { - InternalPedestrianLightState = RoadBaseAI.TrafficLightState.Red; - } - /*} else { - InternalPedestrianLightState = null; - }*/ - - OnChange(calculateAutoPedLight); + if (InternalPedestrianLightState == null) { + InternalPedestrianLightState = RoadBaseAI.TrafficLightState.Red; + } + /*} else { + InternalPedestrianLightState = null; + }*/ + + OnChange(calculateAutoPedLight); #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Housekeeping complete. VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()} CustomLights={CustomLights.DictionaryToString()}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Housekeeping complete. VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()} CustomLights={CustomLights.DictionaryToString()}"); #endif - } - } -} + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs index 0bcb59de0..f0517d42b 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs @@ -1,498 +1,494 @@ #define DEBUGTTLx -using System; -using System.Collections.Generic; -using ColossalFramework; -using TrafficManager.Custom.AI; -using TrafficManager.Geometry; -using TrafficManager.Traffic; -using TrafficManager.Manager; -using System.Linq; -using TrafficManager.Util; -using System.Threading; -using TrafficManager.State; -using GenericGameBridge.Service; -using CSUtil.Commons; -using TrafficManager.Geometry.Impl; -using CSUtil.Commons.Benchmark; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - namespace TrafficManager.TrafficLight.Impl { - // TODO define TimedTrafficLights per node group, not per individual nodes - public class TimedTrafficLights : ITimedTrafficLights { - public ushort NodeId { - get; private set; - } - - /// - /// In case the traffic light is set for a group of nodes, the master node decides - /// if all member steps are done. - /// - public ushort MasterNodeId { - get; set; // TODO private set - } - - public List Steps = new List(); - public int CurrentStep { get; set; } = 0; - - public IList NodeGroup { get; set; } // TODO private set - public bool TestMode { get; set; } = false; // TODO private set - - private bool started = false; - - /// - /// Indicates the total amount and direction of rotation that was applied to this timed traffic light - /// - public short RotationOffset { get; private set; } = 0; - - public IDictionary> Directions { get; private set; } = null; - - /// - /// Segment ends that were set up for this timed traffic light - /// - private ICollection segmentEndIds = new HashSet(); - - public override string ToString() { - return $"[TimedTrafficLights\n" + - "\t" + $"NodeId = {NodeId}\n" + - "\t" + $"masterNodeId = {MasterNodeId}\n" + - "\t" + $"Steps = {Steps.CollectionToString()}\n" + - "\t" + $"NodeGroup = {NodeGroup.CollectionToString()}\n" + - "\t" + $"testMode = {TestMode}\n" + - "\t" + $"started = {started}\n" + - "\t" + $"Directions = {Directions.DictionaryToString()}\n" + - "\t" + $"segmentEndIds = {segmentEndIds.CollectionToString()}\n" + - "TimedTrafficLights]"; - } - - public TimedTrafficLights(ushort nodeId, IEnumerable nodeGroup) { - this.NodeId = nodeId; - NodeGroup = new List(nodeGroup); - MasterNodeId = NodeGroup[0]; - - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - UpdateDirections(ref node); - UpdateSegmentEnds(ref node); - return true; - }); - - started = false; - } - - private TimedTrafficLights() { - - } - - public void PasteSteps(ITimedTrafficLights sourceTimedLight) { - Stop(); - Steps.Clear(); - RotationOffset = 0; - - IList clockSortedSourceSegmentIds = new List(); - Constants.ServiceFactory.NetService.IterateNodeSegments(sourceTimedLight.NodeId, ClockDirection.Clockwise, delegate (ushort segmentId, ref NetSegment segment) { - clockSortedSourceSegmentIds.Add(segmentId); - return true; - }); - - IList clockSortedTargetSegmentIds = new List(); - Constants.ServiceFactory.NetService.IterateNodeSegments(NodeId, ClockDirection.Clockwise, delegate (ushort segmentId, ref NetSegment segment) { - clockSortedTargetSegmentIds.Add(segmentId); - return true; - }); - - if (clockSortedTargetSegmentIds.Count != clockSortedSourceSegmentIds.Count) { - throw new Exception($"TimedTrafficLights.PasteLight: Segment count mismatch -- source node {sourceTimedLight.NodeId}: {clockSortedSourceSegmentIds.CollectionToString()} vs. target node {NodeId}: {clockSortedTargetSegmentIds.CollectionToString()}"); - } - - for (int stepIndex = 0; stepIndex < sourceTimedLight.NumSteps(); ++stepIndex) { - ITimedTrafficLightsStep sourceStep = sourceTimedLight.GetStep(stepIndex); - TimedTrafficLightsStep targetStep = new TimedTrafficLightsStep(this, sourceStep.MinTime, sourceStep.MaxTime, sourceStep.ChangeMetric, sourceStep.WaitFlowBalance); - for (int i = 0; i < clockSortedSourceSegmentIds.Count; ++i) { - ushort sourceSegmentId = clockSortedSourceSegmentIds[i]; - ushort targetSegmentId = clockSortedTargetSegmentIds[i]; - - bool targetStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, NodeId); - - ICustomSegmentLights sourceLights = sourceStep.CustomSegmentLights[sourceSegmentId]; - ICustomSegmentLights targetLights = sourceLights.Clone(targetStep, false); - targetStep.SetSegmentLights(targetSegmentId, targetLights); - Constants.ManagerFactory.CustomSegmentLightsManager.ApplyLightModes(targetSegmentId, targetStartNode, targetLights); - } - Steps.Add(targetStep); - } - - if (sourceTimedLight.IsStarted()) { - Start(); - } - } - - private object rotateLock = new object(); - - private void Rotate(ArrowDirection dir) { - if (! IsMasterNode() || NodeGroup.Count != 1 || Steps.Count <= 0) { - return; - } - - Stop(); - - try { - Monitor.Enter(rotateLock); - - Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Rotating timed traffic light."); - - if (dir != ArrowDirection.Left && dir != ArrowDirection.Right) { - throw new NotSupportedException(); - } - - IList clockSortedSegmentIds = new List(); - Constants.ServiceFactory.NetService.IterateNodeSegments(NodeId, dir == ArrowDirection.Right ? ClockDirection.Clockwise : ClockDirection.CounterClockwise, delegate (ushort segmentId, ref NetSegment segment) { - clockSortedSegmentIds.Add(segmentId); - return true; - }); - - Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Clock-sorted segment ids: {clockSortedSegmentIds.CollectionToString()}"); - - if (clockSortedSegmentIds.Count <= 0) { - return; - } - - int stepIndex = -1; - foreach (TimedTrafficLightsStep step in Steps) { - ++stepIndex; - ICustomSegmentLights bufferedLights = null; - for (int sourceIndex = 0; sourceIndex < clockSortedSegmentIds.Count; ++sourceIndex) { - ushort sourceSegmentId = clockSortedSegmentIds[sourceIndex]; - int targetIndex = (sourceIndex + 1) % clockSortedSegmentIds.Count; - ushort targetSegmentId = clockSortedSegmentIds[targetIndex]; - - Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Moving light @ seg. {sourceSegmentId} to seg. {targetSegmentId} @ step {stepIndex}"); - - ICustomSegmentLights sourceLights = sourceIndex == 0 ? step.RemoveSegmentLights(sourceSegmentId) : bufferedLights; - if (sourceLights == null) { - throw new Exception($"TimedTrafficLights.Rotate({dir}): Error occurred while copying custom lights from {sourceSegmentId} to {targetSegmentId} @ step {stepIndex}: sourceLights is null @ sourceIndex={sourceIndex}, targetIndex={targetIndex}"); - } - bufferedLights = step.RemoveSegmentLights(targetSegmentId); - sourceLights.Relocate(targetSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, NodeId)); - if (!step.SetSegmentLights(targetSegmentId, sourceLights)) { - throw new Exception($"TimedTrafficLights.Rotate({dir}): Error occurred while copying custom lights from {sourceSegmentId} to {targetSegmentId} @ step {stepIndex}: could not set lights for target segment @ sourceIndex={sourceIndex}, targetIndex={targetIndex}"); - } - } - } - - switch (dir) { - case ArrowDirection.Left: - RotationOffset = (short)((RotationOffset + clockSortedSegmentIds.Count - 1) % clockSortedSegmentIds.Count); - break; - case ArrowDirection.Right: - RotationOffset = (short)((RotationOffset + 1) % clockSortedSegmentIds.Count); - break; - } - - CurrentStep = 0; - SetLights(true); - } finally { - Monitor.Exit(rotateLock); - } - } - - public void RotateLeft() { - Rotate(ArrowDirection.Left); - } - - public void RotateRight() { - Rotate(ArrowDirection.Right); - } - - private void UpdateDirections(ref NetNode node) { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using API.Traffic.Enums; + using API.TrafficLight; + using CSUtil.Commons; + using GenericGameBridge.Service; + using Geometry.Impl; + using Manager; + using Manager.Impl; + using State; + using Traffic; + using Util; + + // TODO define TimedTrafficLights per node group, not per individual nodes + public class TimedTrafficLights : ITimedTrafficLights { + public ushort NodeId { + get; private set; + } + + /// + /// In case the traffic light is set for a group of nodes, the master node decides + /// if all member steps are done. + /// + public ushort MasterNodeId { + get; set; // TODO private set + } + + public List Steps = new List(); + public int CurrentStep { get; set; } = 0; + + public IList NodeGroup { get; set; } // TODO private set + public bool TestMode { get; set; } = false; // TODO private set + + private bool started = false; + + /// + /// Indicates the total amount and direction of rotation that was applied to this timed traffic light + /// + public short RotationOffset { get; private set; } = 0; + + public IDictionary> Directions { get; private set; } = null; + + /// + /// Segment ends that were set up for this timed traffic light + /// + private ICollection segmentEndIds = new HashSet(); + + public override string ToString() { + return $"[TimedTrafficLights\n" + + "\t" + $"NodeId = {NodeId}\n" + + "\t" + $"masterNodeId = {MasterNodeId}\n" + + "\t" + $"Steps = {Steps.CollectionToString()}\n" + + "\t" + $"NodeGroup = {NodeGroup.CollectionToString()}\n" + + "\t" + $"testMode = {TestMode}\n" + + "\t" + $"started = {started}\n" + + "\t" + $"Directions = {Directions.DictionaryToString()}\n" + + "\t" + $"segmentEndIds = {segmentEndIds.CollectionToString()}\n" + + "TimedTrafficLights]"; + } + + public TimedTrafficLights(ushort nodeId, IEnumerable nodeGroup) { + this.NodeId = nodeId; + NodeGroup = new List(nodeGroup); + MasterNodeId = NodeGroup[0]; + + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + UpdateDirections(ref node); + UpdateSegmentEnds(ref node); + return true; + }); + + started = false; + } + + private TimedTrafficLights() { + + } + + public void PasteSteps(ITimedTrafficLights sourceTimedLight) { + Stop(); + Steps.Clear(); + RotationOffset = 0; + + IList clockSortedSourceSegmentIds = new List(); + Constants.ServiceFactory.NetService.IterateNodeSegments(sourceTimedLight.NodeId, ClockDirection.Clockwise, delegate (ushort segmentId, ref NetSegment segment) { + clockSortedSourceSegmentIds.Add(segmentId); + return true; + }); + + IList clockSortedTargetSegmentIds = new List(); + Constants.ServiceFactory.NetService.IterateNodeSegments(NodeId, ClockDirection.Clockwise, delegate (ushort segmentId, ref NetSegment segment) { + clockSortedTargetSegmentIds.Add(segmentId); + return true; + }); + + if (clockSortedTargetSegmentIds.Count != clockSortedSourceSegmentIds.Count) { + throw new Exception($"TimedTrafficLights.PasteLight: Segment count mismatch -- source node {sourceTimedLight.NodeId}: {clockSortedSourceSegmentIds.CollectionToString()} vs. target node {NodeId}: {clockSortedTargetSegmentIds.CollectionToString()}"); + } + + for (int stepIndex = 0; stepIndex < sourceTimedLight.NumSteps(); ++stepIndex) { + ITimedTrafficLightsStep sourceStep = sourceTimedLight.GetStep(stepIndex); + TimedTrafficLightsStep targetStep = new TimedTrafficLightsStep(this, sourceStep.MinTime, sourceStep.MaxTime, sourceStep.ChangeMetric, sourceStep.WaitFlowBalance); + for (int i = 0; i < clockSortedSourceSegmentIds.Count; ++i) { + ushort sourceSegmentId = clockSortedSourceSegmentIds[i]; + ushort targetSegmentId = clockSortedTargetSegmentIds[i]; + + bool targetStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, NodeId); + + ICustomSegmentLights sourceLights = sourceStep.CustomSegmentLights[sourceSegmentId]; + ICustomSegmentLights targetLights = sourceLights.Clone(targetStep, false); + targetStep.SetSegmentLights(targetSegmentId, targetLights); + Constants.ManagerFactory.CustomSegmentLightsManager.ApplyLightModes(targetSegmentId, targetStartNode, targetLights); + } + Steps.Add(targetStep); + } + + if (sourceTimedLight.IsStarted()) { + Start(); + } + } + + private object rotateLock = new object(); + + private void Rotate(ArrowDirection dir) { + if (! IsMasterNode() || NodeGroup.Count != 1 || Steps.Count <= 0) { + return; + } + + Stop(); + + try { + Monitor.Enter(rotateLock); + + Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Rotating timed traffic light."); + + if (dir != ArrowDirection.Left && dir != ArrowDirection.Right) { + throw new NotSupportedException(); + } + + IList clockSortedSegmentIds = new List(); + Constants.ServiceFactory.NetService.IterateNodeSegments(NodeId, dir == ArrowDirection.Right ? ClockDirection.Clockwise : ClockDirection.CounterClockwise, delegate (ushort segmentId, ref NetSegment segment) { + clockSortedSegmentIds.Add(segmentId); + return true; + }); + + Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Clock-sorted segment ids: {clockSortedSegmentIds.CollectionToString()}"); + + if (clockSortedSegmentIds.Count <= 0) { + return; + } + + int stepIndex = -1; + foreach (TimedTrafficLightsStep step in Steps) { + ++stepIndex; + ICustomSegmentLights bufferedLights = null; + for (int sourceIndex = 0; sourceIndex < clockSortedSegmentIds.Count; ++sourceIndex) { + ushort sourceSegmentId = clockSortedSegmentIds[sourceIndex]; + int targetIndex = (sourceIndex + 1) % clockSortedSegmentIds.Count; + ushort targetSegmentId = clockSortedSegmentIds[targetIndex]; + + Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Moving light @ seg. {sourceSegmentId} to seg. {targetSegmentId} @ step {stepIndex}"); + + ICustomSegmentLights sourceLights = sourceIndex == 0 ? step.RemoveSegmentLights(sourceSegmentId) : bufferedLights; + if (sourceLights == null) { + throw new Exception($"TimedTrafficLights.Rotate({dir}): Error occurred while copying custom lights from {sourceSegmentId} to {targetSegmentId} @ step {stepIndex}: sourceLights is null @ sourceIndex={sourceIndex}, targetIndex={targetIndex}"); + } + bufferedLights = step.RemoveSegmentLights(targetSegmentId); + sourceLights.Relocate(targetSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, NodeId)); + if (!step.SetSegmentLights(targetSegmentId, sourceLights)) { + throw new Exception($"TimedTrafficLights.Rotate({dir}): Error occurred while copying custom lights from {sourceSegmentId} to {targetSegmentId} @ step {stepIndex}: could not set lights for target segment @ sourceIndex={sourceIndex}, targetIndex={targetIndex}"); + } + } + } + + switch (dir) { + case ArrowDirection.Left: + RotationOffset = (short)((RotationOffset + clockSortedSegmentIds.Count - 1) % clockSortedSegmentIds.Count); + break; + case ArrowDirection.Right: + RotationOffset = (short)((RotationOffset + 1) % clockSortedSegmentIds.Count); + break; + } + + CurrentStep = 0; + SetLights(true); + } finally { + Monitor.Exit(rotateLock); + } + } + + public void RotateLeft() { + Rotate(ArrowDirection.Left); + } + + public void RotateRight() { + Rotate(ArrowDirection.Right); + } + + private void UpdateDirections(ref NetNode node) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == NodeId); - if (debug) - Log._Debug($">>>>> TimedTrafficLights.UpdateDirections: called for node {NodeId}"); + bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == NodeId); + if (debug) + Log._Debug($">>>>> TimedTrafficLights.UpdateDirections: called for node {NodeId}"); #endif - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - Directions = new TinyDictionary>(); - for (int i = 0; i < 8; ++i) { - ushort sourceSegmentId = node.GetSegment(i); + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + Directions = new TinyDictionary>(); + for (int i = 0; i < 8; ++i) { + ushort sourceSegmentId = node.GetSegment(i); - if (sourceSegmentId == 0) - continue; + if (sourceSegmentId == 0) + continue; #if DEBUG - if (debug) - Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}"); #endif - IDictionary dirs = new TinyDictionary(); - Directions.Add(sourceSegmentId, dirs); - int endIndex = segEndMan.GetIndex(sourceSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(sourceSegmentId, NodeId)); + IDictionary dirs = new TinyDictionary(); + Directions.Add(sourceSegmentId, dirs); + int endIndex = segEndMan.GetIndex(sourceSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(sourceSegmentId, NodeId)); - for (int k = 0; k < 8; ++k) { - ushort targetSegmentId = node.GetSegment(k); + for (int k = 0; k < 8; ++k) { + ushort targetSegmentId = node.GetSegment(k); - if (targetSegmentId == 0) - continue; + if (targetSegmentId == 0) + continue; - ArrowDirection dir = segEndMan.GetDirection(ref segEndMan.ExtSegmentEnds[endIndex], targetSegmentId); - dirs.Add(targetSegmentId, dir); + ArrowDirection dir = segEndMan.GetDirection(ref segEndMan.ExtSegmentEnds[endIndex], targetSegmentId); + dirs.Add(targetSegmentId, dir); #if DEBUG - if (debug) - Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}, target segment {targetSegmentId}: adding dir {dir}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}, target segment {targetSegmentId}: adding dir {dir}"); #endif - } - } + } + } #if DEBUG - if (debug) - Log._Debug($"<<<<< TimedTrafficLights.UpdateDirections: finished for node {NodeId}: {Directions.DictionaryToString()}"); + if (debug) + Log._Debug($"<<<<< TimedTrafficLights.UpdateDirections: finished for node {NodeId}: {Directions.DictionaryToString()}"); #endif - } - - public bool IsMasterNode() { - return MasterNodeId == NodeId; - } - - public ITimedTrafficLightsStep AddStep(int minTime, int maxTime, StepChangeMetric changeMetric, float waitFlowBalance, bool makeRed = false) { - // TODO currently, this method must be called for each node in the node group individually - - if (minTime < 0) - minTime = 0; - if (maxTime <= 0) - maxTime = 1; - if (maxTime < minTime) - maxTime = minTime; - - TimedTrafficLightsStep step = new TimedTrafficLightsStep(this, minTime, maxTime, changeMetric, waitFlowBalance, makeRed); - Steps.Add(step); - return step; - } - - public void Start() { - Start(0); - } - - public void Start(int stepIndex) { - // TODO currently, this method must be called for each node in the node group individually - - if (stepIndex < 0 || stepIndex >= Steps.Count) { - stepIndex = 0; - } - - /*if (!housekeeping()) - return;*/ - - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nodeId, ref NetNode node) { - Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(NodeId, ref node); - return true; - }); - - foreach (TimedTrafficLightsStep step in Steps) { - foreach (KeyValuePair e in step.CustomSegmentLights) { - e.Value.Housekeeping(true, true); - } - } - - CheckInvalidPedestrianLights(); - - CurrentStep = stepIndex; - Steps[stepIndex].Start(); - Steps[stepIndex].UpdateLiveLights(); - - started = true; - } - - private void CheckInvalidPedestrianLights() { - ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; - - //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); - for (int i = 0; i < 8; ++i) { - ushort segmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - segmentId = node.GetSegment(i); - return true; - }); - - if (segmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - - ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); - if (lights == null) { - Log.Warning($"TimedTrafficLights.CheckInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {startNode}."); - continue; - } - - //Log._Debug($"Checking seg. {segmentId} @ {NodeId}."); - bool needsAlwaysGreenPedestrian = true; - int s = 0; - foreach (TimedTrafficLightsStep step in Steps) { - //Log._Debug($"Checking step {s}, seg. {segmentId} @ {NodeId}."); - if (!step.CustomSegmentLights.ContainsKey(segmentId)) { - //Log._Debug($"Step {s} @ {NodeId} does not contain a segment light for seg. {segmentId}."); - ++s; - continue; - } - //Log._Debug($"Checking step {s}, seg. {segmentId} @ {NodeId}: {step.segmentLights[segmentId].PedestrianLightState} (pedestrianLightState={step.segmentLights[segmentId].pedestrianLightState}, ManualPedestrianMode={step.segmentLights[segmentId].ManualPedestrianMode}, AutoPedestrianLightState={step.segmentLights[segmentId].AutoPedestrianLightState}"); - if (step.CustomSegmentLights[segmentId].PedestrianLightState == RoadBaseAI.TrafficLightState.Green) { - //Log._Debug($"Step {s} @ {NodeId} has a green ped. light @ seg. {segmentId}."); - needsAlwaysGreenPedestrian = false; - break; - } - ++s; - } - //Log._Debug($"Setting InvalidPedestrianLight of seg. {segmentId} @ {NodeId} to {needsAlwaysGreenPedestrian}."); - lights.InvalidPedestrianLight = needsAlwaysGreenPedestrian; - } - } - - private void ClearInvalidPedestrianLights() { - ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; - - for (int i = 0; i < 8; ++i) { - ushort segmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - segmentId = node.GetSegment(i); - return true; - }); - - if (segmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - - ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); - if (lights == null) { - Log.Warning($"TimedTrafficLights.ClearInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {startNode}."); - continue; - } - lights.InvalidPedestrianLight = false; - } - } - - public void RemoveNodeFromGroup(ushort otherNodeId) { - // TODO currently, this method must be called for each node in the node group individually - - NodeGroup.Remove(otherNodeId); - if (NodeGroup.Count <= 0) { - Constants.ManagerFactory.TrafficLightSimulationManager.RemoveNodeFromSimulation(NodeId, true, false); - return; - } - MasterNodeId = NodeGroup[0]; - } - - // TODO improve & remove - public bool Housekeeping() { - // TODO currently, this method must be called for each node in the node group individually - //Log._Debug($"Housekeeping timed light @ {NodeId}"); - - if (NodeGroup == null || NodeGroup.Count <= 0) { - Stop(); - return false; - } - - //Log.Warning($"Timed housekeeping: Setting master node to {NodeGroup[0]}"); - MasterNodeId = NodeGroup[0]; - - if (IsStarted()) - CheckInvalidPedestrianLights(); - - int i = 0; - foreach (TimedTrafficLightsStep step in Steps) { - foreach (CustomSegmentLights lights in step.CustomSegmentLights.Values) { - //Log._Debug($"----- Housekeeping timed light at step {i}, seg. {lights.SegmentId} @ {NodeId}"); - Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(lights.SegmentId, lights.StartNode).Housekeeping(true, true); - lights.Housekeeping(true, true); - } - ++i; - } - - return true; - } - - public void MoveStep(int oldPos, int newPos) { - // TODO currently, this method must be called for each node in the node group individually - - var oldStep = Steps[oldPos]; - - Steps.RemoveAt(oldPos); - Steps.Insert(newPos, oldStep); - } - - public void Stop() { - // TODO currently, this method must be called for each node in the node group individually - - started = false; - foreach (TimedTrafficLightsStep step in Steps) { - step.Reset(); - } - ClearInvalidPedestrianLights(); - } - - ~TimedTrafficLights() { - Destroy(); - } - - public void Destroy() { - // TODO currently, this method must be called for each node in the node group individually - - started = false; - DestroySegmentEnds(); - Steps = null; - NodeGroup = null; - } - - public bool IsStarted() { - // TODO currently, this method must be called for each node in the node group individually + } + + public bool IsMasterNode() { + return MasterNodeId == NodeId; + } + + public ITimedTrafficLightsStep AddStep(int minTime, int maxTime, StepChangeMetric changeMetric, float waitFlowBalance, bool makeRed = false) { + // TODO currently, this method must be called for each node in the node group individually + + if (minTime < 0) + minTime = 0; + if (maxTime <= 0) + maxTime = 1; + if (maxTime < minTime) + maxTime = minTime; + + TimedTrafficLightsStep step = new TimedTrafficLightsStep(this, minTime, maxTime, changeMetric, waitFlowBalance, makeRed); + Steps.Add(step); + return step; + } + + public void Start() { + Start(0); + } + + public void Start(int stepIndex) { + // TODO currently, this method must be called for each node in the node group individually + + if (stepIndex < 0 || stepIndex >= Steps.Count) { + stepIndex = 0; + } + + /*if (!housekeeping()) + return;*/ + + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nodeId, ref NetNode node) { + Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(NodeId, ref node); + return true; + }); + + foreach (TimedTrafficLightsStep step in Steps) { + foreach (KeyValuePair e in step.CustomSegmentLights) { + e.Value.Housekeeping(true, true); + } + } + + CheckInvalidPedestrianLights(); + + CurrentStep = stepIndex; + Steps[stepIndex].Start(); + Steps[stepIndex].UpdateLiveLights(); + + started = true; + } + + private void CheckInvalidPedestrianLights() { + ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; + + //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); + for (int i = 0; i < 8; ++i) { + ushort segmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + segmentId = node.GetSegment(i); + return true; + }); + + if (segmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + + ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); + if (lights == null) { + Log.Warning($"TimedTrafficLights.CheckInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {startNode}."); + continue; + } + + //Log._Debug($"Checking seg. {segmentId} @ {NodeId}."); + bool needsAlwaysGreenPedestrian = true; + int s = 0; + foreach (TimedTrafficLightsStep step in Steps) { + //Log._Debug($"Checking step {s}, seg. {segmentId} @ {NodeId}."); + if (!step.CustomSegmentLights.ContainsKey(segmentId)) { + //Log._Debug($"Step {s} @ {NodeId} does not contain a segment light for seg. {segmentId}."); + ++s; + continue; + } + //Log._Debug($"Checking step {s}, seg. {segmentId} @ {NodeId}: {step.segmentLights[segmentId].PedestrianLightState} (pedestrianLightState={step.segmentLights[segmentId].pedestrianLightState}, ManualPedestrianMode={step.segmentLights[segmentId].ManualPedestrianMode}, AutoPedestrianLightState={step.segmentLights[segmentId].AutoPedestrianLightState}"); + if (step.CustomSegmentLights[segmentId].PedestrianLightState == RoadBaseAI.TrafficLightState.Green) { + //Log._Debug($"Step {s} @ {NodeId} has a green ped. light @ seg. {segmentId}."); + needsAlwaysGreenPedestrian = false; + break; + } + ++s; + } + //Log._Debug($"Setting InvalidPedestrianLight of seg. {segmentId} @ {NodeId} to {needsAlwaysGreenPedestrian}."); + lights.InvalidPedestrianLight = needsAlwaysGreenPedestrian; + } + } + + private void ClearInvalidPedestrianLights() { + ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; + + for (int i = 0; i < 8; ++i) { + ushort segmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + segmentId = node.GetSegment(i); + return true; + }); + + if (segmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + + ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); + if (lights == null) { + Log.Warning($"TimedTrafficLights.ClearInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {startNode}."); + continue; + } + lights.InvalidPedestrianLight = false; + } + } + + public void RemoveNodeFromGroup(ushort otherNodeId) { + // TODO currently, this method must be called for each node in the node group individually + + NodeGroup.Remove(otherNodeId); + if (NodeGroup.Count <= 0) { + Constants.ManagerFactory.TrafficLightSimulationManager.RemoveNodeFromSimulation(NodeId, true, false); + return; + } + MasterNodeId = NodeGroup[0]; + } + + // TODO improve & remove + public bool Housekeeping() { + // TODO currently, this method must be called for each node in the node group individually + //Log._Debug($"Housekeeping timed light @ {NodeId}"); + + if (NodeGroup == null || NodeGroup.Count <= 0) { + Stop(); + return false; + } + + //Log.Warning($"Timed housekeeping: Setting master node to {NodeGroup[0]}"); + MasterNodeId = NodeGroup[0]; + + if (IsStarted()) + CheckInvalidPedestrianLights(); + + int i = 0; + foreach (TimedTrafficLightsStep step in Steps) { + foreach (CustomSegmentLights lights in step.CustomSegmentLights.Values) { + //Log._Debug($"----- Housekeeping timed light at step {i}, seg. {lights.SegmentId} @ {NodeId}"); + Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(lights.SegmentId, lights.StartNode).Housekeeping(true, true); + lights.Housekeeping(true, true); + } + ++i; + } + + return true; + } + + public void MoveStep(int oldPos, int newPos) { + // TODO currently, this method must be called for each node in the node group individually + + var oldStep = Steps[oldPos]; + + Steps.RemoveAt(oldPos); + Steps.Insert(newPos, oldStep); + } + + public void Stop() { + // TODO currently, this method must be called for each node in the node group individually + + started = false; + foreach (TimedTrafficLightsStep step in Steps) { + step.Reset(); + } + ClearInvalidPedestrianLights(); + } + + ~TimedTrafficLights() { + Destroy(); + } + + public void Destroy() { + // TODO currently, this method must be called for each node in the node group individually + + started = false; + DestroySegmentEnds(); + Steps = null; + NodeGroup = null; + } + + public bool IsStarted() { + // TODO currently, this method must be called for each node in the node group individually - return started; - } - - public int NumSteps() { - // TODO currently, this method must be called for each node in the node group individually - - return Steps.Count; - } + return started; + } + + public int NumSteps() { + // TODO currently, this method must be called for each node in the node group individually + + return Steps.Count; + } - public ITimedTrafficLightsStep GetStep(int stepId) { - // TODO currently, this method must be called for each node in the node group individually + public ITimedTrafficLightsStep GetStep(int stepId) { + // TODO currently, this method must be called for each node in the node group individually - return Steps[stepId]; - } - - public void SimulationStep() { - // TODO this method is currently called on each node, but should be called on the master node only + return Steps[stepId]; + } + + public void SimulationStep() { + // TODO this method is currently called on each node, but should be called on the master node only #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif - if (!IsMasterNode() || !IsStarted()) { + if (!IsMasterNode() || !IsStarted()) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} isMasterNode={IsMasterNode()} IsStarted={IsStarted()}"); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} isMasterNode={IsMasterNode()} IsStarted={IsStarted()}"); #endif - return; - } - // we are the master node + return; + } + // we are the master node - /*if (!housekeeping()) { + /*if (!housekeeping()) { #if DEBUGTTL - Log.Warning($"TTL SimStep: *STOP* NodeId={NodeId} Housekeeping detected that this timed traffic light has become invalid: {NodeId}."); + Log.Warning($"TTL SimStep: *STOP* NodeId={NodeId} Housekeeping detected that this timed traffic light has become invalid: {NodeId}."); #endif - Stop(); - return; - }*/ + Stop(); + return; + }*/ #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (1)"); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (1)"); #endif #if BENCHMARK //using (var bm = new Benchmark(null, "SetLights.1")) { #endif - SetLights(); + SetLights(); #if BENCHMARK //} #endif @@ -500,135 +496,135 @@ public void SimulationStep() { #if BENCHMARK //using (var bm = new Benchmark(null, "StepDone")) { #endif - if (!Steps[CurrentStep].StepDone(true)) { + if (!Steps[CurrentStep].StepDone(true)) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}) is not done."); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}) is not done."); #endif - return; - } + return; + } #if BENCHMARK //} #endif - // step is done + // step is done #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (2)"); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (2)"); #endif - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - if (Steps[CurrentStep].NextStepRefIndex < 0) { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + if (Steps[CurrentStep].NextStepRefIndex < 0) { #if DEBUGTTL - if (debug) { - Log._Debug($"TimedTrafficLights.SimulationStep(): Step {CurrentStep} is done at timed light {NodeId}. Determining next step."); - } + if (debug) { + Log._Debug($"TimedTrafficLights.SimulationStep(): Step {CurrentStep} is done at timed light {NodeId}. Determining next step."); + } #endif - // next step has not yet identified yet. check for minTime=0 steps - int nextStepIndex = (CurrentStep + 1) % NumSteps(); - if (Steps[nextStepIndex].MinTime == 0 && Steps[nextStepIndex].ChangeMetric == Steps[CurrentStep].ChangeMetric) { + // next step has not yet identified yet. check for minTime=0 steps + int nextStepIndex = (CurrentStep + 1) % NumSteps(); + if (Steps[nextStepIndex].MinTime == 0 && Steps[nextStepIndex].ChangeMetric == Steps[CurrentStep].ChangeMetric) { #if BENCHMARK //using (var bm = new Benchmark(null, "bestNextStepIndex")) { #endif - // next step has minTime=0. calculate flow/wait ratios and compare. - int prevStepIndex = CurrentStep; + // next step has minTime=0. calculate flow/wait ratios and compare. + int prevStepIndex = CurrentStep; - // Steps[CurrentStep].minFlow - Steps[CurrentStep].maxWait - float maxWaitFlowDiff = Steps[CurrentStep].GetMetric(Steps[CurrentStep].CurrentFlow, Steps[CurrentStep].CurrentWait); - if (float.IsNaN(maxWaitFlowDiff)) - maxWaitFlowDiff = float.MinValue; - int bestNextStepIndex = prevStepIndex; + // Steps[CurrentStep].minFlow - Steps[CurrentStep].maxWait + float maxWaitFlowDiff = Steps[CurrentStep].GetMetric(Steps[CurrentStep].CurrentFlow, Steps[CurrentStep].CurrentWait); + if (float.IsNaN(maxWaitFlowDiff)) + maxWaitFlowDiff = float.MinValue; + int bestNextStepIndex = prevStepIndex; #if DEBUGTTL - if (debug) { - Log._Debug($"TimedTrafficLights.SimulationStep(): Next step {nextStepIndex} has minTime = 0 at timed light {NodeId}. Old step {CurrentStep} has waitFlowDiff={maxWaitFlowDiff} (flow={Steps[CurrentStep].CurrentFlow}, wait={Steps[CurrentStep].CurrentWait})."); - } + if (debug) { + Log._Debug($"TimedTrafficLights.SimulationStep(): Next step {nextStepIndex} has minTime = 0 at timed light {NodeId}. Old step {CurrentStep} has waitFlowDiff={maxWaitFlowDiff} (flow={Steps[CurrentStep].CurrentFlow}, wait={Steps[CurrentStep].CurrentWait})."); + } #endif - while (nextStepIndex != prevStepIndex) { - float wait; - float flow; - Steps[nextStepIndex].CalcWaitFlow(false, nextStepIndex, out wait, out flow); + while (nextStepIndex != prevStepIndex) { + float wait; + float flow; + Steps[nextStepIndex].CalcWaitFlow(false, nextStepIndex, out wait, out flow); - //float flowWaitDiff = flow - wait; - float flowWaitDiff = Steps[nextStepIndex].GetMetric(flow, wait); - if (flowWaitDiff > maxWaitFlowDiff) { - maxWaitFlowDiff = flowWaitDiff; - bestNextStepIndex = nextStepIndex; - } + //float flowWaitDiff = flow - wait; + float flowWaitDiff = Steps[nextStepIndex].GetMetric(flow, wait); + if (flowWaitDiff > maxWaitFlowDiff) { + maxWaitFlowDiff = flowWaitDiff; + bestNextStepIndex = nextStepIndex; + } #if DEBUGTTL - if (debug) { - Log._Debug($"TimedTrafficLights.SimulationStep(): Checking upcoming step {nextStepIndex} @ node {NodeId}: flow={flow} wait={wait} minTime={Steps[nextStepIndex].MinTime}. bestWaitFlowDiff={bestNextStepIndex}, bestNextStepIndex={bestNextStepIndex}"); - } + if (debug) { + Log._Debug($"TimedTrafficLights.SimulationStep(): Checking upcoming step {nextStepIndex} @ node {NodeId}: flow={flow} wait={wait} minTime={Steps[nextStepIndex].MinTime}. bestWaitFlowDiff={bestNextStepIndex}, bestNextStepIndex={bestNextStepIndex}"); + } #endif - if (Steps[nextStepIndex].MinTime != 0) { - int stepAfterPrev = (prevStepIndex + 1) % NumSteps(); - if (nextStepIndex == stepAfterPrev) { - // always switch if the next step has a minimum time assigned - bestNextStepIndex = stepAfterPrev; - } - break; - } + if (Steps[nextStepIndex].MinTime != 0) { + int stepAfterPrev = (prevStepIndex + 1) % NumSteps(); + if (nextStepIndex == stepAfterPrev) { + // always switch if the next step has a minimum time assigned + bestNextStepIndex = stepAfterPrev; + } + break; + } - nextStepIndex = (nextStepIndex + 1) % NumSteps(); + nextStepIndex = (nextStepIndex + 1) % NumSteps(); - if (Steps[nextStepIndex].ChangeMetric != Steps[CurrentStep].ChangeMetric) { - break; - } - } + if (Steps[nextStepIndex].ChangeMetric != Steps[CurrentStep].ChangeMetric) { + break; + } + } - if (bestNextStepIndex == CurrentStep) { + if (bestNextStepIndex == CurrentStep) { #if DEBUGTTL - if (debug) { - Log._Debug($"TimedTrafficLights.SimulationStep(): Best next step {bestNextStepIndex} (wait/flow diff = {maxWaitFlowDiff}) equals CurrentStep @ node {NodeId}."); - } + if (debug) { + Log._Debug($"TimedTrafficLights.SimulationStep(): Best next step {bestNextStepIndex} (wait/flow diff = {maxWaitFlowDiff}) equals CurrentStep @ node {NodeId}."); + } #endif - // restart the current step - foreach (ushort slaveNodeId in NodeGroup) { - if (! tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } - - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - slaveTTL.GetStep(CurrentStep).Start(CurrentStep); - slaveTTL.GetStep(CurrentStep).UpdateLiveLights(); - } - return; - } else { + // restart the current step + foreach (ushort slaveNodeId in NodeGroup) { + if (! tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } + + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + slaveTTL.GetStep(CurrentStep).Start(CurrentStep); + slaveTTL.GetStep(CurrentStep).UpdateLiveLights(); + } + return; + } else { #if DEBUGTTL - if (debug) { - Log._Debug($"TimedTrafficLights.SimulationStep(): Best next step {bestNextStepIndex} (wait/flow diff = {maxWaitFlowDiff}) does not equal CurrentStep @ node {NodeId}."); - } + if (debug) { + Log._Debug($"TimedTrafficLights.SimulationStep(): Best next step {bestNextStepIndex} (wait/flow diff = {maxWaitFlowDiff}) does not equal CurrentStep @ node {NodeId}."); + } #endif - // set next step reference index for assuring a correct end transition - foreach (ushort slaveNodeId in NodeGroup) { - if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } + // set next step reference index for assuring a correct end transition + foreach (ushort slaveNodeId in NodeGroup) { + if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - slaveTTL.GetStep(CurrentStep).NextStepRefIndex = bestNextStepIndex; - } - } + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + slaveTTL.GetStep(CurrentStep).NextStepRefIndex = bestNextStepIndex; + } + } #if BENCHMARK //} #endif - } else { - Steps[CurrentStep].NextStepRefIndex = nextStepIndex; - } - } + } else { + Steps[CurrentStep].NextStepRefIndex = nextStepIndex; + } + } #if BENCHMARK //using (var bm = new Benchmark(null, "SetLights.2")) { #endif - SetLights(); // check if this is needed + SetLights(); // check if this is needed #if BENCHMARK //} #endif @@ -636,554 +632,559 @@ public void SimulationStep() { #if BENCHMARK //using (var bm = new Benchmark(null, "IsEndTransitionDone")) { #endif - if (!Steps[CurrentStep].IsEndTransitionDone()) { + if (!Steps[CurrentStep].IsEndTransitionDone()) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}): end transition is not done."); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}): end transition is not done."); #endif - return; - } + return; + } #if BENCHMARK //} #endif - // ending transition (yellow) finished + // ending transition (yellow) finished #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} ending transition done. NodeGroup={string.Join(", ", NodeGroup.Select(x => x.ToString()).ToArray())}, nodeId={NodeId}, NumSteps={NumSteps()}"); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} ending transition done. NodeGroup={string.Join(", ", NodeGroup.Select(x => x.ToString()).ToArray())}, nodeId={NodeId}, NumSteps={NumSteps()}"); #endif #if BENCHMARK //using (var bm = new Benchmark(null, "ChangeStep")) { #endif - // change step - int newStepIndex = Steps[CurrentStep].NextStepRefIndex; - int oldStepIndex = CurrentStep; + // change step + int newStepIndex = Steps[CurrentStep].NextStepRefIndex; + int oldStepIndex = CurrentStep; - foreach (ushort slaveNodeId in NodeGroup) { - if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } + foreach (ushort slaveNodeId in NodeGroup) { + if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - slaveTTL.CurrentStep = newStepIndex; + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + slaveTTL.CurrentStep = newStepIndex; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={slaveNodeId} setting lights of next step: {CurrentStep}"); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={slaveNodeId} setting lights of next step: {CurrentStep}"); #endif - slaveTTL.GetStep(oldStepIndex).NextStepRefIndex = -1; - slaveTTL.GetStep(newStepIndex).Start(oldStepIndex); - slaveTTL.GetStep(newStepIndex).UpdateLiveLights(); - } + slaveTTL.GetStep(oldStepIndex).NextStepRefIndex = -1; + slaveTTL.GetStep(newStepIndex).Start(oldStepIndex); + slaveTTL.GetStep(newStepIndex).UpdateLiveLights(); + } #if BENCHMARK //} #endif - } - - public void SetLights(bool noTransition=false) { - if (Steps.Count <= 0) { - return; - } - - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - // set lights - foreach (ushort slaveNodeId in NodeGroup) { - if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } - - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - slaveTTL.GetStep(CurrentStep).UpdateLiveLights(noTransition); - } - } - - public void SkipStep(bool setLights=true, int prevStepRefIndex=-1) { - if (!IsMasterNode()) - return; - - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - var newCurrentStep = (CurrentStep + 1) % NumSteps(); - foreach (ushort slaveNodeId in NodeGroup) { - if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } - - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - - slaveTTL.GetStep(CurrentStep).SetStepDone(); - slaveTTL.CurrentStep = newCurrentStep; - slaveTTL.GetStep(newCurrentStep).Start(prevStepRefIndex); - if (setLights) { - slaveTTL.GetStep(newCurrentStep).UpdateLiveLights(); - } - } - } - - public long CheckNextChange(ushort segmentId, bool startNode, ExtVehicleType vehicleType, int lightType) { - var curStep = CurrentStep; - var nextStep = (CurrentStep + 1) % NumSteps(); - var numFrames = Steps[CurrentStep].MaxTimeRemaining(); - - RoadBaseAI.TrafficLightState currentState; - ICustomSegmentLights segmentLights = Constants.ManagerFactory.CustomSegmentLightsManager.GetSegmentLights(segmentId, startNode, false); - if (segmentLights == null) { - Log._Debug($"CheckNextChange: No segment lights at node {NodeId}, segment {segmentId}"); + } + + public void SetLights(bool noTransition=false) { + if (Steps.Count <= 0) { + return; + } + + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + // set lights + foreach (ushort slaveNodeId in NodeGroup) { + if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } + + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + slaveTTL.GetStep(CurrentStep).UpdateLiveLights(noTransition); + } + } + + public void SkipStep(bool setLights=true, int prevStepRefIndex=-1) { + if (!IsMasterNode()) + return; + + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + var newCurrentStep = (CurrentStep + 1) % NumSteps(); + foreach (ushort slaveNodeId in NodeGroup) { + if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } + + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + + slaveTTL.GetStep(CurrentStep).SetStepDone(); + slaveTTL.CurrentStep = newCurrentStep; + slaveTTL.GetStep(newCurrentStep).Start(prevStepRefIndex); + if (setLights) { + slaveTTL.GetStep(newCurrentStep).UpdateLiveLights(); + } + } + } + + public long CheckNextChange(ushort segmentId, + bool startNode, + API.Traffic.Enums.ExtVehicleType vehicleType, + int lightType) { + var curStep = CurrentStep; + var nextStep = (CurrentStep + 1) % NumSteps(); + var numFrames = Steps[CurrentStep].MaxTimeRemaining(); + + RoadBaseAI.TrafficLightState currentState; + ICustomSegmentLights segmentLights = Constants.ManagerFactory.CustomSegmentLightsManager.GetSegmentLights(segmentId, startNode, false); + if (segmentLights == null) { + Log._Debug($"CheckNextChange: No segment lights at node {NodeId}, segment {segmentId}"); + return 99; + } + ICustomSegmentLight segmentLight = segmentLights.GetCustomLight(vehicleType); + if (segmentLight == null) { + Log._Debug($"CheckNextChange: No segment light at node {NodeId}, segment {segmentId}"); return 99; - } - ICustomSegmentLight segmentLight = segmentLights.GetCustomLight(vehicleType); - if (segmentLight == null) { - Log._Debug($"CheckNextChange: No segment light at node {NodeId}, segment {segmentId}"); - return 99; - } - - if (lightType == 0) - currentState = segmentLight.LightMain; - else if (lightType == 1) - currentState = segmentLight.LightLeft; - else if (lightType == 2) - currentState = segmentLight.LightRight; - else - currentState = segmentLights.PedestrianLightState == null ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)segmentLights.PedestrianLightState; - - - while (true) { - if (nextStep == curStep) { - numFrames = 99; - break; - } - - var light = Steps[nextStep].GetLightState(segmentId, vehicleType, lightType); - - if (light != currentState) { - break; - } else { - numFrames += Steps[nextStep].MaxTime; - } - - nextStep = (nextStep + 1) % NumSteps(); - } - - return numFrames; - } - - public void ResetSteps() { - Steps.Clear(); - } - - public void RemoveStep(int id) { - Steps.RemoveAt(id); - } - - public void OnGeometryUpdate() { - Log._Trace($"TimedTrafficLights.OnGeometryUpdate: called for timed traffic light @ {NodeId}."); - - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - UpdateDirections(ref node); - UpdateSegmentEnds(ref node); - return true; - }); - - if (NumSteps() <= 0) { - Log._Debug($"TimedTrafficLights.OnGeometryUpdate: no steps @ {NodeId}"); - return; - } - - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - BackUpInvalidStepSegments(ref node); - HandleNewSegments(ref node); - return true; - }); - } - - /// - /// Moves all custom segment lights that are associated with an invalid segment to a special container for later reuse - /// - private void BackUpInvalidStepSegments(ref NetNode node) { + } + + if (lightType == 0) + currentState = segmentLight.LightMain; + else if (lightType == 1) + currentState = segmentLight.LightLeft; + else if (lightType == 2) + currentState = segmentLight.LightRight; + else + currentState = segmentLights.PedestrianLightState == null ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)segmentLights.PedestrianLightState; + + + while (true) { + if (nextStep == curStep) { + numFrames = 99; + break; + } + + var light = Steps[nextStep].GetLightState(segmentId, vehicleType, lightType); + + if (light != currentState) { + break; + } else { + numFrames += Steps[nextStep].MaxTime; + } + + nextStep = (nextStep + 1) % NumSteps(); + } + + return numFrames; + } + + public void ResetSteps() { + Steps.Clear(); + } + + public void RemoveStep(int id) { + Steps.RemoveAt(id); + } + + public void OnGeometryUpdate() { + Log._Trace($"TimedTrafficLights.OnGeometryUpdate: called for timed traffic light @ {NodeId}."); + + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + UpdateDirections(ref node); + UpdateSegmentEnds(ref node); + return true; + }); + + if (NumSteps() <= 0) { + Log._Debug($"TimedTrafficLights.OnGeometryUpdate: no steps @ {NodeId}"); + return; + } + + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + BackUpInvalidStepSegments(ref node); + HandleNewSegments(ref node); + return true; + }); + } + + /// + /// Moves all custom segment lights that are associated with an invalid segment to a special container for later reuse + /// + private void BackUpInvalidStepSegments(ref NetNode node) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: called for timed traffic light @ {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: called for timed traffic light @ {NodeId}"); #endif - ICollection validSegments = new HashSet(); - for (int k = 0; k < 8; ++k) { - ushort segmentId = node.GetSegment(k); - - if (segmentId == 0) { - continue; - } + ICollection validSegments = new HashSet(); + for (int k = 0; k < 8; ++k) { + ushort segmentId = node.GetSegment(k); + + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - validSegments.Add(segmentId); - } + validSegments.Add(segmentId); + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: valid segments @ {NodeId}: {validSegments.CollectionToString()}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: valid segments @ {NodeId}: {validSegments.CollectionToString()}"); #endif - int i = 0; - foreach (TimedTrafficLightsStep step in Steps) { - ICollection invalidSegmentIds = new HashSet(); - foreach (KeyValuePair e in step.CustomSegmentLights) { - if (! validSegments.Contains(e.Key)) { - step.InvalidSegmentLights.AddLast(e.Value); - invalidSegmentIds.Add(e.Key); + int i = 0; + foreach (TimedTrafficLightsStep step in Steps) { + ICollection invalidSegmentIds = new HashSet(); + foreach (KeyValuePair e in step.CustomSegmentLights) { + if (! validSegments.Contains(e.Key)) { + step.InvalidSegmentLights.AddLast(e.Value); + invalidSegmentIds.Add(e.Key); #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Detected invalid segment @ step {i}, node {NodeId}: {e.Key}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Detected invalid segment @ step {i}, node {NodeId}: {e.Key}"); #endif - } - } + } + } - foreach (ushort invalidSegmentId in invalidSegmentIds) { + foreach (ushort invalidSegmentId in invalidSegmentIds) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Removing invalid segment {invalidSegmentId} from step {i} @ node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Removing invalid segment {invalidSegmentId} from step {i} @ node {NodeId}"); #endif - step.CustomSegmentLights.Remove(invalidSegmentId); - } + step.CustomSegmentLights.Remove(invalidSegmentId); + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments finished for TTL step {i} @ node {NodeId}: step.CustomSegmentLights={step.CustomSegmentLights.DictionaryToString()} step.InvalidSegmentLights={step.InvalidSegmentLights.CollectionToString()}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments finished for TTL step {i} @ node {NodeId}: step.CustomSegmentLights={step.CustomSegmentLights.DictionaryToString()} step.InvalidSegmentLights={step.InvalidSegmentLights.CollectionToString()}"); #endif - ++i; - } - } + ++i; + } + } - /// - /// Processes new segments and adds them to the steps. If steps contain a custom light - /// for an old invalid segment, this light is being reused for the new segment. - /// - /// - private void HandleNewSegments(ref NetNode node) { + /// + /// Processes new segments and adds them to the steps. If steps contain a custom light + /// for an old invalid segment, this light is being reused for the new segment. + /// + /// + private void HandleNewSegments(ref NetNode node) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif - ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; - ITrafficPriorityManager prioMan = Constants.ManagerFactory.TrafficPriorityManager; + ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; + ITrafficPriorityManager prioMan = Constants.ManagerFactory.TrafficPriorityManager; - //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); - for (int k = 0; k < 8; ++k) { - ushort segmentId = node.GetSegment(k); + //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); + for (int k = 0; k < 8; ++k) { + ushort segmentId = node.GetSegment(k); - if (segmentId == 0) { - continue; - } + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.HandleNewSegments: handling existing seg. {segmentId} @ {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.HandleNewSegments: handling existing seg. {segmentId} @ {NodeId}"); #endif - if (Steps[0].CustomSegmentLights.ContainsKey(segmentId)) { - continue; - } + if (Steps[0].CustomSegmentLights.ContainsKey(segmentId)) { + continue; + } - // segment was created - RotationOffset = 0; + // segment was created + RotationOffset = 0; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.HandleNewSegments: New segment detected: {segmentId} @ {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.HandleNewSegments: New segment detected: {segmentId} @ {NodeId}"); #endif - int stepIndex = -1; - foreach (TimedTrafficLightsStep step in Steps) { - ++stepIndex; + int stepIndex = -1; + foreach (TimedTrafficLightsStep step in Steps) { + ++stepIndex; - LinkedListNode lightsToReuseNode = step.InvalidSegmentLights.First; - if (lightsToReuseNode == null) { - // no old segment found: create a fresh custom light + LinkedListNode lightsToReuseNode = step.InvalidSegmentLights.First; + if (lightsToReuseNode == null) { + // no old segment found: create a fresh custom light #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.HandleNewSegments: Adding new segment {segmentId} to node {NodeId} without reusing old segment"); + if (debug) + Log._Debug($"TimedTrafficLights.HandleNewSegments: Adding new segment {segmentId} to node {NodeId} without reusing old segment"); #endif - if (! step.AddSegment(segmentId, startNode, true)) { + if (! step.AddSegment(segmentId, startNode, true)) { #if DEBUGTTL - if (debug) - Log.Warning($"TimedTrafficLights.HandleNewSegments: Failed to add segment {segmentId} @ start {startNode} to node {NodeId}"); + if (debug) + Log.Warning($"TimedTrafficLights.HandleNewSegments: Failed to add segment {segmentId} @ start {startNode} to node {NodeId}"); #endif - } - } else { - // reuse old lights - step.InvalidSegmentLights.RemoveFirst(); - ICustomSegmentLights lightsToReuse = lightsToReuseNode.Value; + } + } else { + // reuse old lights + step.InvalidSegmentLights.RemoveFirst(); + ICustomSegmentLights lightsToReuse = lightsToReuseNode.Value; #if DEBUGTTL - if (debug) - Log._Debug($"Replacing old segment @ {NodeId} with new segment {segmentId}"); + if (debug) + Log._Debug($"Replacing old segment @ {NodeId} with new segment {segmentId}"); #endif - lightsToReuse.Relocate(segmentId, startNode); - step.SetSegmentLights(segmentId, lightsToReuse); - } - } - } - } - - public ITimedTrafficLights MasterLights() { - return TrafficLightSimulationManager.Instance.TrafficLightSimulations[MasterNodeId].timedLight; - } - - public void SetTestMode(bool testMode) { - this.TestMode = false; - if (!IsStarted()) - return; - this.TestMode = testMode; - } - - public bool IsInTestMode() { - if (!IsStarted()) { - TestMode = false; - } - return TestMode; - } - - public void ChangeLightMode(ushort segmentId, ExtVehicleType vehicleType, LightMode mode) { + lightsToReuse.Relocate(segmentId, startNode); + step.SetSegmentLights(segmentId, lightsToReuse); + } + } + } + } + + public ITimedTrafficLights MasterLights() { + return TrafficLightSimulationManager.Instance.TrafficLightSimulations[MasterNodeId].timedLight; + } + + public void SetTestMode(bool testMode) { + this.TestMode = false; + if (!IsStarted()) + return; + this.TestMode = testMode; + } + + public bool IsInTestMode() { + if (!IsStarted()) { + TestMode = false; + } + return TestMode; + } + + public void ChangeLightMode(ushort segmentId, + API.Traffic.Enums.ExtVehicleType vehicleType, + LightMode mode) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif - if (! Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + if (! Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { #if DEBUGTTL - if (debug) - Log.Error($"TimedTrafficLights.ChangeLightMode: Segment {segmentId} is invalid"); + if (debug) + Log.Error($"TimedTrafficLights.ChangeLightMode: Segment {segmentId} is invalid"); #endif - return; - } - - bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - if (startNode == null) { - return; - } - - foreach (TimedTrafficLightsStep step in Steps) { - step.ChangeLightMode(segmentId, vehicleType, mode); - } - Constants.ManagerFactory.CustomSegmentLightsManager.SetLightMode(segmentId, (bool)startNode, vehicleType, mode); - } - - public void Join(ITimedTrafficLights otherTimedLight) { - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - if (NumSteps() < otherTimedLight.NumSteps()) { - // increase the number of steps at our timed lights - for (int i = NumSteps(); i < otherTimedLight.NumSteps(); ++i) { - ITimedTrafficLightsStep otherStep = otherTimedLight.GetStep(i); - foreach (ushort slaveNodeId in NodeGroup) { - if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } - - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - slaveTTL.AddStep(otherStep.MinTime, otherStep.MaxTime, otherStep.ChangeMetric, otherStep.WaitFlowBalance, true); - } - } - } else { - // increase the number of steps at their timed lights - for (int i = otherTimedLight.NumSteps(); i < NumSteps(); ++i) { - ITimedTrafficLightsStep ourStep = GetStep(i); - foreach (ushort slaveNodeId in otherTimedLight.NodeGroup) { - if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } - - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - slaveTTL.AddStep(ourStep.MinTime, ourStep.MaxTime, ourStep.ChangeMetric, ourStep.WaitFlowBalance, true); - } - } - } - - // join groups and re-defined master node, determine mean min/max times & mean wait-flow-balances - HashSet newNodeGroupSet = new HashSet(); - newNodeGroupSet.UnionWith(NodeGroup); - newNodeGroupSet.UnionWith(otherTimedLight.NodeGroup); - List newNodeGroup = new List(newNodeGroupSet); - ushort newMasterNodeId = newNodeGroup[0]; - - int[] minTimes = new int[NumSteps()]; - int[] maxTimes = new int[NumSteps()]; - float[] waitFlowBalances = new float[NumSteps()]; - StepChangeMetric?[] stepChangeMetrics = new StepChangeMetric?[NumSteps()]; - - foreach (ushort timedNodeId in newNodeGroup) { - if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { - continue; - } - ITimedTrafficLights ttl = tlsMan.TrafficLightSimulations[timedNodeId].timedLight; - - for (int i = 0; i < NumSteps(); ++i) { - minTimes[i] += ttl.GetStep(i).MinTime; - maxTimes[i] += ttl.GetStep(i).MaxTime; - waitFlowBalances[i] += ttl.GetStep(i).WaitFlowBalance; - StepChangeMetric metric = ttl.GetStep(i).ChangeMetric; - if (metric != StepChangeMetric.Default) { - if (stepChangeMetrics[i] == null) { - // remember first non-default setting - stepChangeMetrics[i] = metric; - } else if (stepChangeMetrics[i] != metric) { - // reset back to default on metric mismatch - stepChangeMetrics[i] = StepChangeMetric.Default; - } - } - } - - ttl.NodeGroup = newNodeGroup; - ttl.MasterNodeId = newMasterNodeId; - } - - // build means - if (NumSteps() > 0) { - for (int i = 0; i < NumSteps(); ++i) { - minTimes[i] = Math.Max(0, minTimes[i] / newNodeGroup.Count); - maxTimes[i] = Math.Max(1, maxTimes[i] / newNodeGroup.Count); - waitFlowBalances[i] = Math.Max(0.001f, waitFlowBalances[i] / (float)newNodeGroup.Count); - } - } - - // apply means & reset - foreach (ushort timedNodeId in newNodeGroup) { - if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { - continue; - } - - ITimedTrafficLights ttl = tlsMan.TrafficLightSimulations[timedNodeId].timedLight; - - ttl.Stop(); - ttl.TestMode = false; - for (int i = 0; i < NumSteps(); ++i) { - ttl.GetStep(i).MinTime = minTimes[i]; - ttl.GetStep(i).MaxTime = maxTimes[i]; - ttl.GetStep(i).WaitFlowBalance = waitFlowBalances[i]; - ttl.GetStep(i).ChangeMetric = stepChangeMetrics[i] == null ? StepChangeMetric.Default : (StepChangeMetric)stepChangeMetrics[i]; - } - } - } - - private void UpdateSegmentEnds(ref NetNode node) { + return; + } + + bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + if (startNode == null) { + return; + } + + foreach (TimedTrafficLightsStep step in Steps) { + step.ChangeLightMode(segmentId, vehicleType, mode); + } + Constants.ManagerFactory.CustomSegmentLightsManager.SetLightMode(segmentId, (bool)startNode, vehicleType, mode); + } + + public void Join(ITimedTrafficLights otherTimedLight) { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + if (NumSteps() < otherTimedLight.NumSteps()) { + // increase the number of steps at our timed lights + for (int i = NumSteps(); i < otherTimedLight.NumSteps(); ++i) { + ITimedTrafficLightsStep otherStep = otherTimedLight.GetStep(i); + foreach (ushort slaveNodeId in NodeGroup) { + if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } + + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + slaveTTL.AddStep(otherStep.MinTime, otherStep.MaxTime, otherStep.ChangeMetric, otherStep.WaitFlowBalance, true); + } + } + } else { + // increase the number of steps at their timed lights + for (int i = otherTimedLight.NumSteps(); i < NumSteps(); ++i) { + ITimedTrafficLightsStep ourStep = GetStep(i); + foreach (ushort slaveNodeId in otherTimedLight.NodeGroup) { + if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } + + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + slaveTTL.AddStep(ourStep.MinTime, ourStep.MaxTime, ourStep.ChangeMetric, ourStep.WaitFlowBalance, true); + } + } + } + + // join groups and re-defined master node, determine mean min/max times & mean wait-flow-balances + HashSet newNodeGroupSet = new HashSet(); + newNodeGroupSet.UnionWith(NodeGroup); + newNodeGroupSet.UnionWith(otherTimedLight.NodeGroup); + List newNodeGroup = new List(newNodeGroupSet); + ushort newMasterNodeId = newNodeGroup[0]; + + int[] minTimes = new int[NumSteps()]; + int[] maxTimes = new int[NumSteps()]; + float[] waitFlowBalances = new float[NumSteps()]; + StepChangeMetric?[] stepChangeMetrics = new StepChangeMetric?[NumSteps()]; + + foreach (ushort timedNodeId in newNodeGroup) { + if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { + continue; + } + ITimedTrafficLights ttl = tlsMan.TrafficLightSimulations[timedNodeId].timedLight; + + for (int i = 0; i < NumSteps(); ++i) { + minTimes[i] += ttl.GetStep(i).MinTime; + maxTimes[i] += ttl.GetStep(i).MaxTime; + waitFlowBalances[i] += ttl.GetStep(i).WaitFlowBalance; + StepChangeMetric metric = ttl.GetStep(i).ChangeMetric; + if (metric != StepChangeMetric.Default) { + if (stepChangeMetrics[i] == null) { + // remember first non-default setting + stepChangeMetrics[i] = metric; + } else if (stepChangeMetrics[i] != metric) { + // reset back to default on metric mismatch + stepChangeMetrics[i] = StepChangeMetric.Default; + } + } + } + + ttl.NodeGroup = newNodeGroup; + ttl.MasterNodeId = newMasterNodeId; + } + + // build means + if (NumSteps() > 0) { + for (int i = 0; i < NumSteps(); ++i) { + minTimes[i] = Math.Max(0, minTimes[i] / newNodeGroup.Count); + maxTimes[i] = Math.Max(1, maxTimes[i] / newNodeGroup.Count); + waitFlowBalances[i] = Math.Max(0.001f, waitFlowBalances[i] / (float)newNodeGroup.Count); + } + } + + // apply means & reset + foreach (ushort timedNodeId in newNodeGroup) { + if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { + continue; + } + + ITimedTrafficLights ttl = tlsMan.TrafficLightSimulations[timedNodeId].timedLight; + + ttl.Stop(); + ttl.TestMode = false; + for (int i = 0; i < NumSteps(); ++i) { + ttl.GetStep(i).MinTime = minTimes[i]; + ttl.GetStep(i).MaxTime = maxTimes[i]; + ttl.GetStep(i).WaitFlowBalance = waitFlowBalances[i]; + ttl.GetStep(i).ChangeMetric = stepChangeMetrics[i] == null ? StepChangeMetric.Default : (StepChangeMetric)stepChangeMetrics[i]; + } + } + } + + private void UpdateSegmentEnds(ref NetNode node) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: called for node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: called for node {NodeId}"); #endif - ISegmentEndManager segEndMan = Constants.ManagerFactory.SegmentEndManager; + ISegmentEndManager segEndMan = Constants.ManagerFactory.SegmentEndManager; - ICollection segmentEndsToDelete = new HashSet(); - // update currently set segment ends - foreach (ISegmentEndId endId in segmentEndIds) { + ICollection segmentEndsToDelete = new HashSet(); + // update currently set segment ends + foreach (ISegmentEndId endId in segmentEndIds) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: updating existing segment end {endId} for node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: updating existing segment end {endId} for node {NodeId}"); #endif - if (!segEndMan.UpdateSegmentEnd(endId)) { + if (!segEndMan.UpdateSegmentEnd(endId)) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is invalid"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is invalid"); #endif - segmentEndsToDelete.Add(endId); - } else { - ISegmentEnd end = segEndMan.GetSegmentEnd(endId); - if (end.NodeId != NodeId) { + segmentEndsToDelete.Add(endId); + } else { + ISegmentEnd end = segEndMan.GetSegmentEnd(endId); + if (end.NodeId != NodeId) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Segment end {end} is valid and updated but does not belong to TTL node {NodeId} anymore."); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Segment end {end} is valid and updated but does not belong to TTL node {NodeId} anymore."); #endif - segmentEndsToDelete.Add(endId); - } else { + segmentEndsToDelete.Add(endId); + } else { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is valid"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is valid"); #endif - } - } - } + } + } + } - // remove all invalid segment ends - foreach (ISegmentEndId endId in segmentEndsToDelete) { + // remove all invalid segment ends + foreach (ISegmentEndId endId in segmentEndsToDelete) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Removing invalid segment end {endId} @ node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Removing invalid segment end {endId} @ node {NodeId}"); #endif - segmentEndIds.Remove(endId); - } + segmentEndIds.Remove(endId); + } - // set up new segment ends + // set up new segment ends #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Setting up new segment ends @ node {NodeId}."); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Setting up new segment ends @ node {NodeId}."); #endif - for (int i = 0; i < 8; ++i) { - ushort segmentId = node.GetSegment(i); + for (int i = 0; i < 8; ++i) { + ushort segmentId = node.GetSegment(i); - if (segmentId == 0) { - continue; - } + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - ISegmentEndId endId = new SegmentEndId(segmentId, startNode); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + ISegmentEndId endId = new SegmentEndId(segmentId, startNode); - if (segmentEndIds.Contains(endId)) { + if (segmentEndIds.Contains(endId)) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Node {NodeId} already knows segment {segmentId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Node {NodeId} already knows segment {segmentId}"); #endif - continue; - } + continue; + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Adding segment {segmentId} to node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Adding segment {segmentId} to node {NodeId}"); #endif - ISegmentEnd end = segEndMan.GetOrAddSegmentEnd(segmentId, startNode); - if (end != null) { - segmentEndIds.Add(end); - } else { + ISegmentEnd end = segEndMan.GetOrAddSegmentEnd(segmentId, startNode); + if (end != null) { + segmentEndIds.Add(end); + } else { #if DEBUGTTL - if (debug) - Log.Warning($"TimedTrafficLights.UpdateSegmentEnds: Failed to add segment end {segmentId} @ {startNode} to node {NodeId}: GetOrAddSegmentEnd returned null."); + if (debug) + Log.Warning($"TimedTrafficLights.UpdateSegmentEnds: Failed to add segment end {segmentId} @ {startNode} to node {NodeId}: GetOrAddSegmentEnd returned null."); #endif - } - } + } + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: finished for node {NodeId}: {segmentEndIds.CollectionToString()}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: finished for node {NodeId}: {segmentEndIds.CollectionToString()}"); #endif - } + } - private void DestroySegmentEnds() { + private void DestroySegmentEnds() { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; - if (debug) - Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment ends @ node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment ends @ node {NodeId}"); #endif - foreach (ISegmentEndId endId in segmentEndIds) { + foreach (ISegmentEndId endId in segmentEndIds) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment end {endId} @ node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment end {endId} @ node {NodeId}"); #endif - // TODO only remove if no priority sign is located at the segment end (although this is currently not possible) - Constants.ManagerFactory.SegmentEndManager.RemoveSegmentEnd(endId); - } - segmentEndIds.Clear(); + // TODO only remove if no priority sign is located at the segment end (although this is currently not possible) + Constants.ManagerFactory.SegmentEndManager.RemoveSegmentEnd(endId); + } + segmentEndIds.Clear(); #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.DestroySegmentEnds: finished for node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.DestroySegmentEnds: finished for node {NodeId}"); #endif - } - } -} + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs index d812b107e..9f1ed9946 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs @@ -2,1097 +2,1100 @@ #define DEBUGTTLx #define DEBUGMETRICx -using System; -using System.Collections.Generic; -using ColossalFramework; -using TrafficManager.TrafficLight; -using TrafficManager.Geometry; -using TrafficManager.Custom.AI; -using TrafficManager.Traffic; -using TrafficManager.Manager; -using TrafficManager.State; -using TrafficManager.Util; -using System.Linq; -using CSUtil.Commons; -using TrafficManager.Geometry.Impl; -using CSUtil.Commons.Benchmark; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic.Enums; - namespace TrafficManager.TrafficLight.Impl { - // TODO class should be completely reworked, approx. in version 1.10 - public class TimedTrafficLightsStep : ITimedTrafficLightsStep { - /// - /// The number of time units this traffic light remains in the current state at least - /// - public int MinTime { get; set; } - - /// - /// The number of time units this traffic light remains in the current state at most - /// - public int MaxTime { get; set; } - - /// - /// Indicates if waiting vehicles should be measured - /// - public StepChangeMetric ChangeMetric { get; set; } - - public uint startFrame; - - /// - /// Indicates if the step is done (internal use only) - /// - private bool stepDone; - - /// - /// Frame when the GreenToRed phase started - /// - private uint? endTransitionStart; - - /// - /// minimum mean "number of cars passing through" / "average segment length" - /// - public float CurrentFlow { get; private set; } - - /// - /// maximum mean "number of cars waiting for green" / "average segment length" - /// - public float CurrentWait { get; private set; } - - public int PreviousStepRefIndex { get; set; } = -1; - public int NextStepRefIndex { get; set; } = -1; - - public uint lastFlowWaitCalc = 0; - - private ITimedTrafficLights timedNode; - - public IDictionary CustomSegmentLights { get; private set; } = new TinyDictionary(); - public LinkedList InvalidSegmentLights { get; private set; } = new LinkedList(); - - public float WaitFlowBalance { get; set; } = 1f; - - public override string ToString() { - return $"[TimedTrafficLightsStep\n" + - "\t" + $"minTime = {MinTime}\n" + - "\t" + $"maxTime = {MaxTime}\n" + - "\t" + $"stepChangeMode = {ChangeMetric}\n" + - "\t" + $"startFrame = {startFrame}\n" + - "\t" + $"stepDone = {stepDone}\n" + - "\t" + $"endTransitionStart = {endTransitionStart}\n" + - "\t" + $"minFlow = {CurrentFlow}\n" + - "\t" + $"maxWait = {CurrentWait}\n" + - "\t" + $"PreviousStepRefIndex = {PreviousStepRefIndex}\n" + - "\t" + $"NextStepRefIndex = {NextStepRefIndex}\n" + - "\t" + $"lastFlowWaitCalc = {lastFlowWaitCalc}\n" + - "\t" + $"CustomSegmentLights = {CustomSegmentLights}\n" + - "\t" + $"InvalidSegmentLights = {InvalidSegmentLights.CollectionToString()}\n" + - "\t" + $"waitFlowBalance = {WaitFlowBalance}\n" + - "TimedTrafficLightsStep]"; - } - - public TimedTrafficLightsStep(ITimedTrafficLights timedNode, int minTime, int maxTime, StepChangeMetric stepChangeMode, float waitFlowBalance, bool makeRed=false) { - this.MinTime = minTime; - this.MaxTime = maxTime; - this.ChangeMetric = stepChangeMode; - this.WaitFlowBalance = waitFlowBalance; - this.timedNode = timedNode; - - CurrentFlow = Single.NaN; - CurrentWait = Single.NaN; - - endTransitionStart = null; - stepDone = false; - - for (int i = 0; i < 8; ++i) { - ushort segmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(timedNode.NodeId, delegate (ushort nId, ref NetNode node) { - segmentId = node.GetSegment(i); - return true; - }); - - if (segmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); - if (!AddSegment(segmentId, startNode, makeRed)) { - Log.Warning($"TimedTrafficLightsStep.ctor: Failed to add segment {segmentId} @ start {startNode} to node {timedNode.NodeId}"); - } - } - } - - private TimedTrafficLightsStep() { - - } - - /// - /// Checks if the green-to-red (=yellow) phase is finished - /// - /// - public bool IsEndTransitionDone() { - if (!timedNode.IsMasterNode()) { - ITimedTrafficLights masterLights = timedNode.MasterLights(); - return masterLights.GetStep(masterLights.CurrentStep).IsEndTransitionDone(); - } - - bool ret = endTransitionStart != null && getCurrentFrame() > endTransitionStart && stepDone; //StepDone(false); + using System; + using System.Collections.Generic; + using System.Linq; + using API.Manager; + using API.Traffic.Enums; + using API.TrafficLight; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using State; + using Traffic; + using Util; + + // TODO class should be completely reworked, approx. in version 1.10 + public class TimedTrafficLightsStep : ITimedTrafficLightsStep { + /// + /// The number of time units this traffic light remains in the current state at least + /// + public int MinTime { get; set; } + + /// + /// The number of time units this traffic light remains in the current state at most + /// + public int MaxTime { get; set; } + + /// + /// Indicates if waiting vehicles should be measured + /// + public StepChangeMetric ChangeMetric { get; set; } + + public uint startFrame; + + /// + /// Indicates if the step is done (internal use only) + /// + private bool stepDone; + + /// + /// Frame when the GreenToRed phase started + /// + private uint? endTransitionStart; + + /// + /// minimum mean "number of cars passing through" / "average segment length" + /// + public float CurrentFlow { get; private set; } + + /// + /// maximum mean "number of cars waiting for green" / "average segment length" + /// + public float CurrentWait { get; private set; } + + public int PreviousStepRefIndex { get; set; } = -1; + public int NextStepRefIndex { get; set; } = -1; + + public uint lastFlowWaitCalc = 0; + + private ITimedTrafficLights timedNode; + + public IDictionary CustomSegmentLights { get; private set; } = new TinyDictionary(); + public LinkedList InvalidSegmentLights { get; private set; } = new LinkedList(); + + public float WaitFlowBalance { get; set; } = 1f; + + public override string ToString() { + return $"[TimedTrafficLightsStep\n" + + "\t" + $"minTime = {MinTime}\n" + + "\t" + $"maxTime = {MaxTime}\n" + + "\t" + $"stepChangeMode = {ChangeMetric}\n" + + "\t" + $"startFrame = {startFrame}\n" + + "\t" + $"stepDone = {stepDone}\n" + + "\t" + $"endTransitionStart = {endTransitionStart}\n" + + "\t" + $"minFlow = {CurrentFlow}\n" + + "\t" + $"maxWait = {CurrentWait}\n" + + "\t" + $"PreviousStepRefIndex = {PreviousStepRefIndex}\n" + + "\t" + $"NextStepRefIndex = {NextStepRefIndex}\n" + + "\t" + $"lastFlowWaitCalc = {lastFlowWaitCalc}\n" + + "\t" + $"CustomSegmentLights = {CustomSegmentLights}\n" + + "\t" + $"InvalidSegmentLights = {InvalidSegmentLights.CollectionToString()}\n" + + "\t" + $"waitFlowBalance = {WaitFlowBalance}\n" + + "TimedTrafficLightsStep]"; + } + + public TimedTrafficLightsStep(ITimedTrafficLights timedNode, int minTime, int maxTime, StepChangeMetric stepChangeMode, float waitFlowBalance, bool makeRed=false) { + this.MinTime = minTime; + this.MaxTime = maxTime; + this.ChangeMetric = stepChangeMode; + this.WaitFlowBalance = waitFlowBalance; + this.timedNode = timedNode; + + CurrentFlow = Single.NaN; + CurrentWait = Single.NaN; + + endTransitionStart = null; + stepDone = false; + + for (int i = 0; i < 8; ++i) { + ushort segmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(timedNode.NodeId, delegate (ushort nId, ref NetNode node) { + segmentId = node.GetSegment(i); + return true; + }); + + if (segmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); + if (!AddSegment(segmentId, startNode, makeRed)) { + Log.Warning($"TimedTrafficLightsStep.ctor: Failed to add segment {segmentId} @ start {startNode} to node {timedNode.NodeId}"); + } + } + } + + private TimedTrafficLightsStep() { + + } + + /// + /// Checks if the green-to-red (=yellow) phase is finished + /// + /// + public bool IsEndTransitionDone() { + if (!timedNode.IsMasterNode()) { + ITimedTrafficLights masterLights = timedNode.MasterLights(); + return masterLights.GetStep(masterLights.CurrentStep).IsEndTransitionDone(); + } + + bool ret = endTransitionStart != null && getCurrentFrame() > endTransitionStart && stepDone; //StepDone(false); #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.isEndTransitionDone() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} endTransitionStart={endTransitionStart} stepDone={stepDone} ret={ret}"); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.isEndTransitionDone() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} endTransitionStart={endTransitionStart} stepDone={stepDone} ret={ret}"); #endif - return ret; - } - - /// - /// Checks if the green-to-red (=yellow) phase is currently active - /// - /// - public bool IsInEndTransition() { - if (!timedNode.IsMasterNode()) { - ITimedTrafficLights masterLights = timedNode.MasterLights(); - return masterLights.GetStep(masterLights.CurrentStep).IsInEndTransition(); - } - - bool ret = endTransitionStart != null && getCurrentFrame() <= endTransitionStart && stepDone; //StepDone(false); + return ret; + } + + /// + /// Checks if the green-to-red (=yellow) phase is currently active + /// + /// + public bool IsInEndTransition() { + if (!timedNode.IsMasterNode()) { + ITimedTrafficLights masterLights = timedNode.MasterLights(); + return masterLights.GetStep(masterLights.CurrentStep).IsInEndTransition(); + } + + bool ret = endTransitionStart != null && getCurrentFrame() <= endTransitionStart && stepDone; //StepDone(false); #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.isInEndTransition() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} endTransitionStart={endTransitionStart} stepDone={stepDone} ret={ret}"); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.isInEndTransition() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} endTransitionStart={endTransitionStart} stepDone={stepDone} ret={ret}"); #endif - return ret; - } + return ret; + } - public bool IsInStartTransition() { - if (!timedNode.IsMasterNode()) { - ITimedTrafficLights masterLights = timedNode.MasterLights(); - return masterLights.GetStep(masterLights.CurrentStep).IsInStartTransition(); - } + public bool IsInStartTransition() { + if (!timedNode.IsMasterNode()) { + ITimedTrafficLights masterLights = timedNode.MasterLights(); + return masterLights.GetStep(masterLights.CurrentStep).IsInStartTransition(); + } - bool ret = getCurrentFrame() == startFrame && !stepDone; //!StepDone(false); + bool ret = getCurrentFrame() == startFrame && !stepDone; //!StepDone(false); #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.isInStartTransition() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} startFrame={startFrame} stepDone={stepDone} ret={ret}"); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.isInStartTransition() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} startFrame={startFrame} stepDone={stepDone} ret={ret}"); #endif - return ret; - } - - public RoadBaseAI.TrafficLightState GetLightState(ushort segmentId, ExtVehicleType vehicleType, int lightType) { - ICustomSegmentLight segLight = CustomSegmentLights[segmentId].GetCustomLight(vehicleType); - if (segLight != null) { - switch (lightType) { - case 0: - return segLight.LightMain; - case 1: - return segLight.LightLeft; - case 2: - return segLight.LightRight; - case 3: - RoadBaseAI.TrafficLightState? pedState = CustomSegmentLights[segmentId].PedestrianLightState; - return pedState == null ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)pedState; - } - } - - return RoadBaseAI.TrafficLightState.Green; - } - - /// - /// Starts the step. - /// - public void Start(int previousStepRefIndex=-1) { + return ret; + } + + public RoadBaseAI.TrafficLightState GetLightState(ushort segmentId, + API.Traffic.Enums.ExtVehicleType vehicleType, + int lightType) { + ICustomSegmentLight segLight = CustomSegmentLights[segmentId].GetCustomLight(vehicleType); + if (segLight != null) { + switch (lightType) { + case 0: + return segLight.LightMain; + case 1: + return segLight.LightLeft; + case 2: + return segLight.LightRight; + case 3: + RoadBaseAI.TrafficLightState? pedState = CustomSegmentLights[segmentId].PedestrianLightState; + return pedState == null ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)pedState; + } + } + + return RoadBaseAI.TrafficLightState.Green; + } + + /// + /// Starts the step. + /// + public void Start(int previousStepRefIndex=-1) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.Start: Starting step {timedNode.CurrentStep} @ {timedNode.NodeId}"); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.Start: Starting step {timedNode.CurrentStep} @ {timedNode.NodeId}"); #endif - this.startFrame = getCurrentFrame(); - Reset(); - PreviousStepRefIndex = previousStepRefIndex; + this.startFrame = getCurrentFrame(); + Reset(); + PreviousStepRefIndex = previousStepRefIndex; #if DEBUG - /*if (GlobalConfig.Instance.Debug.Switches[2]) { - if (timedNode.NodeId == 31605) { - Log._Debug($"===== Step {timedNode.CurrentStep} @ node {timedNode.NodeId} ====="); - Log._Debug($"minTime: {minTime} maxTime: {maxTime}"); - foreach (KeyValuePair e in segmentLights) { - Log._Debug($"\tSegment {e.Key}:"); - Log._Debug($"\t{e.Value.ToString()}"); - } - } - }*/ + /*if (GlobalConfig.Instance.Debug.Switches[2]) { + if (timedNode.NodeId == 31605) { + Log._Debug($"===== Step {timedNode.CurrentStep} @ node {timedNode.NodeId} ====="); + Log._Debug($"minTime: {minTime} maxTime: {maxTime}"); + foreach (KeyValuePair e in segmentLights) { + Log._Debug($"\tSegment {e.Key}:"); + Log._Debug($"\t{e.Value.ToString()}"); + } + } + }*/ #endif - } - - internal void Reset() { - this.endTransitionStart = null; - CurrentFlow = Single.NaN; - CurrentWait = Single.NaN; - lastFlowWaitCalc = 0; - PreviousStepRefIndex = -1; - NextStepRefIndex = -1; - stepDone = false; - } - - internal static uint getCurrentFrame() { - return Constants.ServiceFactory.SimulationService.CurrentFrameIndex >> 6; - } - - /// - /// Updates "real-world" traffic light states according to the timed scripts - /// - public void UpdateLiveLights() { - UpdateLiveLights(false); - } - - public void UpdateLiveLights(bool noTransition) { - try { - ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; - - bool atEndTransition = !noTransition && (IsInEndTransition() || IsEndTransitionDone()); // = yellow - bool atStartTransition = !noTransition && !atEndTransition && IsInStartTransition(); // = red + yellow + } + + internal void Reset() { + this.endTransitionStart = null; + CurrentFlow = Single.NaN; + CurrentWait = Single.NaN; + lastFlowWaitCalc = 0; + PreviousStepRefIndex = -1; + NextStepRefIndex = -1; + stepDone = false; + } + + internal static uint getCurrentFrame() { + return Constants.ServiceFactory.SimulationService.CurrentFrameIndex >> 6; + } + + /// + /// Updates "real-world" traffic light states according to the timed scripts + /// + public void UpdateLiveLights() { + UpdateLiveLights(false); + } + + public void UpdateLiveLights(bool noTransition) { + try { + ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; + + bool atEndTransition = !noTransition && (IsInEndTransition() || IsEndTransitionDone()); // = yellow + bool atStartTransition = !noTransition && !atEndTransition && IsInStartTransition(); // = red + yellow #if DEBUGTTL - if (timedNode == null) { - Log.Error($"TimedTrafficLightsStep: timedNode is null!"); - return; - } + if (timedNode == null) { + Log.Error($"TimedTrafficLightsStep: timedNode is null!"); + return; + } #endif - if (PreviousStepRefIndex >= timedNode.NumSteps()) - PreviousStepRefIndex = -1; - if (NextStepRefIndex >= timedNode.NumSteps()) - NextStepRefIndex = -1; - ITimedTrafficLightsStep previousStep = timedNode.GetStep(PreviousStepRefIndex >= 0 ? PreviousStepRefIndex : ((timedNode.CurrentStep + timedNode.NumSteps() - 1) % timedNode.NumSteps())); - ITimedTrafficLightsStep nextStep = timedNode.GetStep(NextStepRefIndex >= 0 ? NextStepRefIndex : ((timedNode.CurrentStep + 1) % timedNode.NumSteps())); + if (PreviousStepRefIndex >= timedNode.NumSteps()) + PreviousStepRefIndex = -1; + if (NextStepRefIndex >= timedNode.NumSteps()) + NextStepRefIndex = -1; + ITimedTrafficLightsStep previousStep = timedNode.GetStep(PreviousStepRefIndex >= 0 ? PreviousStepRefIndex : ((timedNode.CurrentStep + timedNode.NumSteps() - 1) % timedNode.NumSteps())); + ITimedTrafficLightsStep nextStep = timedNode.GetStep(NextStepRefIndex >= 0 ? NextStepRefIndex : ((timedNode.CurrentStep + 1) % timedNode.NumSteps())); #if DEBUGTTL - if (previousStep == null) { - Log.Error($"TimedTrafficLightsStep: previousStep is null!"); - //return; - } - - if (nextStep == null) { - Log.Error($"TimedTrafficLightsStep: nextStep is null!"); - //return; - } - - if (previousStep.CustomSegmentLights == null) { - Log.Error($"TimedTrafficLightsStep: previousStep.segmentLights is null!"); - //return; - } - - if (nextStep.CustomSegmentLights == null) { - Log.Error($"TimedTrafficLightsStep: nextStep.segmentLights is null!"); - //return; - } - - if (CustomSegmentLights == null) { - Log.Error($"TimedTrafficLightsStep: segmentLights is null!"); - //return; - } + if (previousStep == null) { + Log.Error($"TimedTrafficLightsStep: previousStep is null!"); + //return; + } + + if (nextStep == null) { + Log.Error($"TimedTrafficLightsStep: nextStep is null!"); + //return; + } + + if (previousStep.CustomSegmentLights == null) { + Log.Error($"TimedTrafficLightsStep: previousStep.segmentLights is null!"); + //return; + } + + if (nextStep.CustomSegmentLights == null) { + Log.Error($"TimedTrafficLightsStep: nextStep.segmentLights is null!"); + //return; + } + + if (CustomSegmentLights == null) { + Log.Error($"TimedTrafficLightsStep: segmentLights is null!"); + //return; + } #endif #if DEBUG - //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) called for NodeId={timedNode.NodeId}. atStartTransition={atStartTransition} atEndTransition={atEndTransition}"); + //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) called for NodeId={timedNode.NodeId}. atStartTransition={atStartTransition} atEndTransition={atEndTransition}"); #endif - foreach (KeyValuePair e in CustomSegmentLights) { - var segmentId = e.Key; - var curStepSegmentLights = e.Value; + foreach (KeyValuePair e in CustomSegmentLights) { + var segmentId = e.Key; + var curStepSegmentLights = e.Value; #if DEBUG - //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId}"); + //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId}"); #endif - ICustomSegmentLights prevStepSegmentLights = null; - if (!previousStep.CustomSegmentLights.TryGetValue(segmentId, out prevStepSegmentLights)) { + ICustomSegmentLights prevStepSegmentLights = null; + if (!previousStep.CustomSegmentLights.TryGetValue(segmentId, out prevStepSegmentLights)) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log.Warning($"TimedTrafficLightsStep: previousStep does not contain lights for segment {segmentId}!"); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log.Warning($"TimedTrafficLightsStep: previousStep does not contain lights for segment {segmentId}!"); #endif - continue; - } + continue; + } - ICustomSegmentLights nextStepSegmentLights = null; - if (!nextStep.CustomSegmentLights.TryGetValue(segmentId, out nextStepSegmentLights)) { + ICustomSegmentLights nextStepSegmentLights = null; + if (!nextStep.CustomSegmentLights.TryGetValue(segmentId, out nextStepSegmentLights)) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log.Warning($"TimedTrafficLightsStep: nextStep does not contain lights for segment {segmentId}!"); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log.Warning($"TimedTrafficLightsStep: nextStep does not contain lights for segment {segmentId}!"); #endif - continue; - } + continue; + } - //segLightState.makeRedOrGreen(); // TODO temporary fix + //segLightState.makeRedOrGreen(); // TODO temporary fix - ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(segmentId, curStepSegmentLights.StartNode, false); - if (liveSegmentLights == null) { - Log.Warning($"TimedTrafficLightsStep.UpdateLights() @ node {timedNode.NodeId}: Could not retrieve live segment lights for segment {segmentId} @ start {curStepSegmentLights.StartNode}."); - continue; - } + ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(segmentId, curStepSegmentLights.StartNode, false); + if (liveSegmentLights == null) { + Log.Warning($"TimedTrafficLightsStep.UpdateLights() @ node {timedNode.NodeId}: Could not retrieve live segment lights for segment {segmentId} @ start {curStepSegmentLights.StartNode}."); + continue; + } - RoadBaseAI.TrafficLightState pedLightState = calcLightState((RoadBaseAI.TrafficLightState)prevStepSegmentLights.PedestrianLightState, (RoadBaseAI.TrafficLightState)curStepSegmentLights.PedestrianLightState, (RoadBaseAI.TrafficLightState)nextStepSegmentLights.PedestrianLightState, atStartTransition, atEndTransition); - //Log._Debug($"TimedStep.SetLights: Setting pedestrian light state @ seg. {segmentId} to {pedLightState} {curStepSegmentLights.ManualPedestrianMode}"); + RoadBaseAI.TrafficLightState pedLightState = calcLightState((RoadBaseAI.TrafficLightState)prevStepSegmentLights.PedestrianLightState, (RoadBaseAI.TrafficLightState)curStepSegmentLights.PedestrianLightState, (RoadBaseAI.TrafficLightState)nextStepSegmentLights.PedestrianLightState, atStartTransition, atEndTransition); + //Log._Debug($"TimedStep.SetLights: Setting pedestrian light state @ seg. {segmentId} to {pedLightState} {curStepSegmentLights.ManualPedestrianMode}"); liveSegmentLights.ManualPedestrianMode = curStepSegmentLights.ManualPedestrianMode; - liveSegmentLights.PedestrianLightState = liveSegmentLights.AutoPedestrianLightState = pedLightState; - //Log.Warning($"Step @ {timedNode.NodeId}: Segment {segmentId}: Ped.: {liveSegmentLights.PedestrianLightState.ToString()} / {liveSegmentLights.AutoPedestrianLightState.ToString()}"); + liveSegmentLights.PedestrianLightState = liveSegmentLights.AutoPedestrianLightState = pedLightState; + //Log.Warning($"Step @ {timedNode.NodeId}: Segment {segmentId}: Ped.: {liveSegmentLights.PedestrianLightState.ToString()} / {liveSegmentLights.AutoPedestrianLightState.ToString()}"); #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - if (curStepSegmentLights.VehicleTypes == null) { - Log.Error($"TimedTrafficLightsStep: curStepSegmentLights.VehicleTypes is null!"); - return; - } + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + if (curStepSegmentLights.VehicleTypes == null) { + Log.Error($"TimedTrafficLightsStep: curStepSegmentLights.VehicleTypes is null!"); + return; + } #endif - foreach (ExtVehicleType vehicleType in curStepSegmentLights.VehicleTypes) { + foreach (var vehicleType in curStepSegmentLights.VehicleTypes) { #if DEBUG - //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); + //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); #endif - ICustomSegmentLight liveSegmentLight = liveSegmentLights.GetCustomLight(vehicleType); - if (liveSegmentLight == null) { + ICustomSegmentLight liveSegmentLight = liveSegmentLights.GetCustomLight(vehicleType); + if (liveSegmentLight == null) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"Timed step @ seg. {segmentId}, node {timedNode.NodeId} has a traffic light for {vehicleType} but the live segment does not have one."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"Timed step @ seg. {segmentId}, node {timedNode.NodeId} has a traffic light for {vehicleType} but the live segment does not have one."); #endif - continue; - } - ICustomSegmentLight curStepSegmentLight = curStepSegmentLights.GetCustomLight(vehicleType); - ICustomSegmentLight prevStepSegmentLight = prevStepSegmentLights.GetCustomLight(vehicleType); - ICustomSegmentLight nextStepSegmentLight = nextStepSegmentLights.GetCustomLight(vehicleType); - + continue; + } + ICustomSegmentLight curStepSegmentLight = curStepSegmentLights.GetCustomLight(vehicleType); + ICustomSegmentLight prevStepSegmentLight = prevStepSegmentLights.GetCustomLight(vehicleType); + ICustomSegmentLight nextStepSegmentLight = nextStepSegmentLights.GetCustomLight(vehicleType); + #if DEBUGTTL - if (curStepSegmentLight == null) { - Log.Error($"TimedTrafficLightsStep: curStepSegmentLight is null!"); - //return; - } - - if (prevStepSegmentLight == null) { - Log.Error($"TimedTrafficLightsStep: prevStepSegmentLight is null!"); - //return; - } - - if (nextStepSegmentLight == null) { - Log.Error($"TimedTrafficLightsStep: nextStepSegmentLight is null!"); - //return; - } + if (curStepSegmentLight == null) { + Log.Error($"TimedTrafficLightsStep: curStepSegmentLight is null!"); + //return; + } + + if (prevStepSegmentLight == null) { + Log.Error($"TimedTrafficLightsStep: prevStepSegmentLight is null!"); + //return; + } + + if (nextStepSegmentLight == null) { + Log.Error($"TimedTrafficLightsStep: nextStepSegmentLight is null!"); + //return; + } #endif - liveSegmentLight.InternalCurrentMode = curStepSegmentLight.CurrentMode; // TODO improve & remove - /*curStepSegmentLight.EnsureModeLights(); - prevStepSegmentLight.EnsureModeLights(); - nextStepSegmentLight.EnsureModeLights();*/ + liveSegmentLight.InternalCurrentMode = curStepSegmentLight.CurrentMode; // TODO improve & remove + /*curStepSegmentLight.EnsureModeLights(); + prevStepSegmentLight.EnsureModeLights(); + nextStepSegmentLight.EnsureModeLights();*/ - RoadBaseAI.TrafficLightState mainLight = calcLightState(prevStepSegmentLight.LightMain, curStepSegmentLight.LightMain, nextStepSegmentLight.LightMain, atStartTransition, atEndTransition); - RoadBaseAI.TrafficLightState leftLight = calcLightState(prevStepSegmentLight.LightLeft, curStepSegmentLight.LightLeft, nextStepSegmentLight.LightLeft, atStartTransition, atEndTransition); - RoadBaseAI.TrafficLightState rightLight = calcLightState(prevStepSegmentLight.LightRight, curStepSegmentLight.LightRight, nextStepSegmentLight.LightRight, atStartTransition, atEndTransition); - liveSegmentLight.SetStates(mainLight, leftLight, rightLight, false); + RoadBaseAI.TrafficLightState mainLight = calcLightState(prevStepSegmentLight.LightMain, curStepSegmentLight.LightMain, nextStepSegmentLight.LightMain, atStartTransition, atEndTransition); + RoadBaseAI.TrafficLightState leftLight = calcLightState(prevStepSegmentLight.LightLeft, curStepSegmentLight.LightLeft, nextStepSegmentLight.LightLeft, atStartTransition, atEndTransition); + RoadBaseAI.TrafficLightState rightLight = calcLightState(prevStepSegmentLight.LightRight, curStepSegmentLight.LightRight, nextStepSegmentLight.LightRight, atStartTransition, atEndTransition); + liveSegmentLight.SetStates(mainLight, leftLight, rightLight, false); #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> *SETTING* LightLeft={liveSegmentLight.LightLeft} LightMain={liveSegmentLight.LightMain} LightRight={liveSegmentLight.LightRight} for segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> *SETTING* LightLeft={liveSegmentLight.LightLeft} LightMain={liveSegmentLight.LightMain} LightRight={liveSegmentLight.LightRight} for segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); #endif - //Log._Debug($"Step @ {timedNode.NodeId}: Segment {segmentId} for vehicle type {vehicleType}: L: {liveSegmentLight.LightLeft.ToString()} F: {liveSegmentLight.LightMain.ToString()} R: {liveSegmentLight.LightRight.ToString()}"); - } - - /*if (timedNode.NodeId == 20164) { - Log._Debug($"Step @ {timedNode.NodeId}: Segment {segmentId}: {segmentLight.LightLeft.ToString()} {segmentLight.LightMain.ToString()} {segmentLight.LightRight.ToString()} {segmentLight.LightPedestrian.ToString()}"); - }*/ - - liveSegmentLights.UpdateVisuals(); - } - } catch (Exception e) { - Log.Error($"Exception in TimedTrafficStep.UpdateLiveLights for node {timedNode.NodeId}: {e.ToString()}"); - //invalid = true; - } - } - - private RoadBaseAI.TrafficLightState calcLightState(RoadBaseAI.TrafficLightState previousState, RoadBaseAI.TrafficLightState currentState, RoadBaseAI.TrafficLightState nextState, bool atStartTransition, bool atEndTransition) { - if (atStartTransition && currentState == RoadBaseAI.TrafficLightState.Green && previousState == RoadBaseAI.TrafficLightState.Red) - return RoadBaseAI.TrafficLightState.RedToGreen; - else if (atEndTransition && currentState == RoadBaseAI.TrafficLightState.Green && nextState == RoadBaseAI.TrafficLightState.Red) - return RoadBaseAI.TrafficLightState.GreenToRed; - else - return currentState; - } - - /// - /// Updates timed segment lights according to "real-world" traffic light states - /// - public void UpdateLights() { - Log._Debug($"TimedTrafficLightsStep.UpdateLights: Updating lights of timed traffic light step @ {timedNode.NodeId}"); - foreach (KeyValuePair e in CustomSegmentLights) { - var segmentId = e.Key; - ICustomSegmentLights segLights = e.Value; - - Log._Debug($"TimedTrafficLightsStep.UpdateLights: Updating lights of timed traffic light step at seg. {e.Key} @ {timedNode.NodeId}"); - - //if (segment == 0) continue; - ICustomSegmentLights liveSegLights = Constants.ManagerFactory.CustomSegmentLightsManager.GetSegmentLights(segmentId, segLights.StartNode, false); - if (liveSegLights == null) { - Log.Warning($"TimedTrafficLightsStep.UpdateLights() @ node {timedNode.NodeId}: Could not retrieve live segment lights for segment {segmentId} @ start {segLights.StartNode}."); - continue; - } - - segLights.SetLights(liveSegLights); - Log._Debug($"TimedTrafficLightsStep.UpdateLights: Segment {segmentId} AutoPedState={segLights.AutoPedestrianLightState} live={liveSegLights.AutoPedestrianLightState}"); - } - } - - /// - /// Countdown value for min. time - /// - /// - public long MinTimeRemaining() { - return Math.Max(0, startFrame + MinTime - getCurrentFrame()); - } - - /// - /// Countdown value for max. time - /// - /// - public long MaxTimeRemaining() { - return Math.Max(0, startFrame + MaxTime - getCurrentFrame()); - } - - public void SetStepDone() { - stepDone = true; - } - - public bool StepDone(bool updateValues) { + //Log._Debug($"Step @ {timedNode.NodeId}: Segment {segmentId} for vehicle type {vehicleType}: L: {liveSegmentLight.LightLeft.ToString()} F: {liveSegmentLight.LightMain.ToString()} R: {liveSegmentLight.LightRight.ToString()}"); + } + + /*if (timedNode.NodeId == 20164) { + Log._Debug($"Step @ {timedNode.NodeId}: Segment {segmentId}: {segmentLight.LightLeft.ToString()} {segmentLight.LightMain.ToString()} {segmentLight.LightRight.ToString()} {segmentLight.LightPedestrian.ToString()}"); +}*/ + + liveSegmentLights.UpdateVisuals(); + } + } catch (Exception e) { + Log.Error($"Exception in TimedTrafficStep.UpdateLiveLights for node {timedNode.NodeId}: {e.ToString()}"); + //invalid = true; + } + } + + private RoadBaseAI.TrafficLightState calcLightState(RoadBaseAI.TrafficLightState previousState, RoadBaseAI.TrafficLightState currentState, RoadBaseAI.TrafficLightState nextState, bool atStartTransition, bool atEndTransition) { + if (atStartTransition && currentState == RoadBaseAI.TrafficLightState.Green && previousState == RoadBaseAI.TrafficLightState.Red) + return RoadBaseAI.TrafficLightState.RedToGreen; + else if (atEndTransition && currentState == RoadBaseAI.TrafficLightState.Green && nextState == RoadBaseAI.TrafficLightState.Red) + return RoadBaseAI.TrafficLightState.GreenToRed; + else + return currentState; + } + + /// + /// Updates timed segment lights according to "real-world" traffic light states + /// + public void UpdateLights() { + Log._Debug($"TimedTrafficLightsStep.UpdateLights: Updating lights of timed traffic light step @ {timedNode.NodeId}"); + foreach (KeyValuePair e in CustomSegmentLights) { + var segmentId = e.Key; + ICustomSegmentLights segLights = e.Value; + + Log._Debug($"TimedTrafficLightsStep.UpdateLights: Updating lights of timed traffic light step at seg. {e.Key} @ {timedNode.NodeId}"); + + //if (segment == 0) continue; + ICustomSegmentLights liveSegLights = Constants.ManagerFactory.CustomSegmentLightsManager.GetSegmentLights(segmentId, segLights.StartNode, false); + if (liveSegLights == null) { + Log.Warning($"TimedTrafficLightsStep.UpdateLights() @ node {timedNode.NodeId}: Could not retrieve live segment lights for segment {segmentId} @ start {segLights.StartNode}."); + continue; + } + + segLights.SetLights(liveSegLights); + Log._Debug($"TimedTrafficLightsStep.UpdateLights: Segment {segmentId} AutoPedState={segLights.AutoPedestrianLightState} live={liveSegLights.AutoPedestrianLightState}"); + } + } + + /// + /// Countdown value for min. time + /// + /// + public long MinTimeRemaining() { + return Math.Max(0, startFrame + MinTime - getCurrentFrame()); + } + + /// + /// Countdown value for max. time + /// + /// + public long MaxTimeRemaining() { + return Math.Max(0, startFrame + MaxTime - getCurrentFrame()); + } + + public void SetStepDone() { + stepDone = true; + } + + public bool StepDone(bool updateValues) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId; - if (debug) { - Log._Debug($"StepDone: called for node {timedNode.NodeId} @ step {timedNode.CurrentStep}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId; + if (debug) { + Log._Debug($"StepDone: called for node {timedNode.NodeId} @ step {timedNode.CurrentStep}"); + } #endif - if (!timedNode.IsMasterNode()) { - ITimedTrafficLights masterLights = timedNode.MasterLights(); - return masterLights.GetStep(masterLights.CurrentStep).StepDone(updateValues); - } - // we are the master node - - if (timedNode.IsInTestMode()) { - return false; - } - if (stepDone) { - return true; - } - - if (getCurrentFrame() >= startFrame + MaxTime) { - // maximum time reached. switch! + if (!timedNode.IsMasterNode()) { + ITimedTrafficLights masterLights = timedNode.MasterLights(); + return masterLights.GetStep(masterLights.CurrentStep).StepDone(updateValues); + } + // we are the master node + + if (timedNode.IsInTestMode()) { + return false; + } + if (stepDone) { + return true; + } + + if (getCurrentFrame() >= startFrame + MaxTime) { + // maximum time reached. switch! #if DEBUGTTL - if (debug) - Log._Debug($"StepDone: step finished @ {timedNode.NodeId}"); + if (debug) + Log._Debug($"StepDone: step finished @ {timedNode.NodeId}"); #endif - if (!stepDone && updateValues) { - stepDone = true; - endTransitionStart = getCurrentFrame(); - } - return stepDone; - } - - if (getCurrentFrame() >= startFrame + MinTime) { - float wait, flow; - uint curFrame = getCurrentFrame(); - //Log._Debug($"TTL @ {timedNode.NodeId}: curFrame={curFrame} lastFlowWaitCalc={lastFlowWaitCalc}"); - if (lastFlowWaitCalc < curFrame) { - //Log._Debug($"TTL @ {timedNode.NodeId}: lastFlowWaitCalc= startFrame + MinTime) { + float wait, flow; + uint curFrame = getCurrentFrame(); + //Log._Debug($"TTL @ {timedNode.NodeId}: curFrame={curFrame} lastFlowWaitCalc={lastFlowWaitCalc}"); + if (lastFlowWaitCalc < curFrame) { + //Log._Debug($"TTL @ {timedNode.NodeId}: lastFlowWaitCalc=curFrame wait={maxWait} flow={minFlow}"); - } - - float newFlow = CurrentFlow; - float newWait = CurrentWait; + if (updateValues) { + lastFlowWaitCalc = curFrame; + //Log._Debug($"TTL @ {timedNode.NodeId}: updated lastFlowWaitCalc=curFrame={curFrame}"); + } + } else { + flow = CurrentFlow; + wait = CurrentWait; + //Log._Debug($"TTL @ {timedNode.NodeId}: lastFlowWaitCalc>=curFrame wait={maxWait} flow={minFlow}"); + } + + float newFlow = CurrentFlow; + float newWait = CurrentWait; #if DEBUGMETRIC newFlow = flow; newWait = wait; #else - if (ChangeMetric != StepChangeMetric.Default || Single.IsNaN(newFlow)) { - newFlow = flow; - } else { - newFlow = GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor * newFlow + (1f - GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor) * flow; // some smoothing - } - - if (Single.IsNaN(newWait)) { - newWait = 0; - } else if (ChangeMetric != StepChangeMetric.Default) { - newWait = wait; - } else { - newWait = GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor * newWait + (1f - GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor) * wait; // some smoothing - } + if (ChangeMetric != StepChangeMetric.Default || Single.IsNaN(newFlow)) { + newFlow = flow; + } else { + newFlow = GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor * newFlow + (1f - GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor) * flow; // some smoothing + } + + if (Single.IsNaN(newWait)) { + newWait = 0; + } else if (ChangeMetric != StepChangeMetric.Default) { + newWait = wait; + } else { + newWait = GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor * newWait + (1f - GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor) * wait; // some smoothing + } #endif - // if more cars are waiting than flowing, we change the step - float metric; - bool done = ShouldGoToNextStep(newFlow, newWait, out metric);// newWait > 0 && newFlow < newWait; + // if more cars are waiting than flowing, we change the step + float metric; + bool done = ShouldGoToNextStep(newFlow, newWait, out metric);// newWait > 0 && newFlow < newWait; - //Log._Debug($"TTL @ {timedNode.NodeId}: newWait={newWait} newFlow={newFlow} updateValues={updateValues} stepDone={stepDone} done={done}"); + //Log._Debug($"TTL @ {timedNode.NodeId}: newWait={newWait} newFlow={newFlow} updateValues={updateValues} stepDone={stepDone} done={done}"); - if (updateValues) { - CurrentFlow = newFlow; - CurrentWait = newWait; - //Log._Debug($"TTL @ {timedNode.NodeId}: updated minFlow=newFlow={minFlow} maxWait=newWait={maxWait}"); - } + if (updateValues) { + CurrentFlow = newFlow; + CurrentWait = newWait; + //Log._Debug($"TTL @ {timedNode.NodeId}: updated minFlow=newFlow={minFlow} maxWait=newWait={maxWait}"); + } #if DEBUG - //Log.Message("step finished (2) @ " + nodeId); + //Log.Message("step finished (2) @ " + nodeId); #endif - if (updateValues && !stepDone && done) { - stepDone = done; - endTransitionStart = getCurrentFrame(); - } - return done; - } - - return false; - } - - public float GetMetric(float flow, float wait) { - switch (ChangeMetric) { - case StepChangeMetric.Default: - default: - return flow - wait; - case StepChangeMetric.FirstFlow: - return flow <= 0 ? 1f : 0f; - case StepChangeMetric.FirstWait: - return wait <= 0 ? 1f : 0f; - case StepChangeMetric.NoFlow: - return flow > 0 ? 1f : 0f; - case StepChangeMetric.NoWait: - return wait > 0 ? 1f : 0f; - } - } - - public bool ShouldGoToNextStep(float flow, float wait, out float metric) { - metric = GetMetric(flow, wait); - return ChangeMetric == StepChangeMetric.Default ? metric < 0 : metric == 0f; - } - - /// - /// Calculates the current metrics for flowing and waiting vehicles - /// - /// - /// - /// true if the values could be calculated, false otherwise - public void CalcWaitFlow(bool countOnlyMovingIfGreen, int stepRefIndex, out float wait, out float flow) { - uint numFlows = 0; - uint numWaits = 0; - float curTotalFlow = 0; - float curTotalWait = 0; + if (updateValues && !stepDone && done) { + stepDone = done; + endTransitionStart = getCurrentFrame(); + } + return done; + } + + return false; + } + + public float GetMetric(float flow, float wait) { + switch (ChangeMetric) { + case StepChangeMetric.Default: + default: + return flow - wait; + case StepChangeMetric.FirstFlow: + return flow <= 0 ? 1f : 0f; + case StepChangeMetric.FirstWait: + return wait <= 0 ? 1f : 0f; + case StepChangeMetric.NoFlow: + return flow > 0 ? 1f : 0f; + case StepChangeMetric.NoWait: + return wait > 0 ? 1f : 0f; + } + } + + public bool ShouldGoToNextStep(float flow, float wait, out float metric) { + metric = GetMetric(flow, wait); + return ChangeMetric == StepChangeMetric.Default ? metric < 0 : metric == 0f; + } + + /// + /// Calculates the current metrics for flowing and waiting vehicles + /// + /// + /// + /// true if the values could be calculated, false otherwise + public void CalcWaitFlow(bool countOnlyMovingIfGreen, int stepRefIndex, out float wait, out float flow) { + uint numFlows = 0; + uint numWaits = 0; + float curTotalFlow = 0; + float curTotalWait = 0; #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId; - if (debug) { - Log.Warning($"calcWaitFlow: called for node {timedNode.NodeId} @ step {stepRefIndex}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId; + if (debug) { + Log.Warning($"calcWaitFlow: called for node {timedNode.NodeId} @ step {stepRefIndex}"); + } #else bool debug = false; #endif - // TODO checking agains getCurrentFrame() is only valid if this is the current step - if (countOnlyMovingIfGreen && getCurrentFrame() <= startFrame + MinTime + 1) { // during start phase all vehicles on "green" segments are counted as flowing - countOnlyMovingIfGreen = false; - } - - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - ISegmentEndManager endMan = Constants.ManagerFactory.SegmentEndManager; - IVehicleRestrictionsManager restrMan = Constants.ManagerFactory.VehicleRestrictionsManager; - - // loop over all timed traffic lights within the node group - foreach (ushort timedNodeId in timedNode.NodeGroup) { - if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { - continue; - } - - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[timedNodeId].timedLight; - ITimedTrafficLightsStep slaveStep = slaveTTL.GetStep(stepRefIndex); - - // minimum time reached. check traffic! loop over source segments - uint numNodeFlows = 0; - uint numNodeWaits = 0; - float curTotalNodeFlow = 0; - float curTotalNodeWait = 0; - foreach (KeyValuePair e in slaveStep.CustomSegmentLights) { - var sourceSegmentId = e.Key; - var segLights = e.Value; - - IDictionary directions = null; - if (!slaveTTL.Directions.TryGetValue(sourceSegmentId, out directions)) { + // TODO checking agains getCurrentFrame() is only valid if this is the current step + if (countOnlyMovingIfGreen && getCurrentFrame() <= startFrame + MinTime + 1) { // during start phase all vehicles on "green" segments are counted as flowing + countOnlyMovingIfGreen = false; + } + + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + ISegmentEndManager endMan = Constants.ManagerFactory.SegmentEndManager; + IVehicleRestrictionsManager restrMan = Constants.ManagerFactory.VehicleRestrictionsManager; + + // loop over all timed traffic lights within the node group + foreach (ushort timedNodeId in timedNode.NodeGroup) { + if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { + continue; + } + + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[timedNodeId].timedLight; + ITimedTrafficLightsStep slaveStep = slaveTTL.GetStep(stepRefIndex); + + // minimum time reached. check traffic! loop over source segments + uint numNodeFlows = 0; + uint numNodeWaits = 0; + float curTotalNodeFlow = 0; + float curTotalNodeWait = 0; + foreach (KeyValuePair e in slaveStep.CustomSegmentLights) { + var sourceSegmentId = e.Key; + var segLights = e.Value; + + IDictionary directions = null; + if (!slaveTTL.Directions.TryGetValue(sourceSegmentId, out directions)) { #if DEBUGTTL - if (debug) { - Log._Debug($"calcWaitFlow: No arrow directions defined for segment {sourceSegmentId} @ {timedNodeId}"); - } + if (debug) { + Log._Debug($"calcWaitFlow: No arrow directions defined for segment {sourceSegmentId} @ {timedNodeId}"); + } #endif - continue; - } - - // one of the traffic lights at this segment is green: count minimum traffic flowing through - ISegmentEnd sourceSegmentEnd = endMan.GetSegmentEnd(sourceSegmentId, segLights.StartNode); - if (sourceSegmentEnd == null) { - Log.Error($"TimedTrafficLightsStep.calcWaitFlow: No segment end @ seg. {sourceSegmentId} found!"); - continue; // skip invalid segment - } - - IDictionary[] movingVehiclesMetrics = null; - bool countOnlyMovingIfGreenOnSegment = false; - if (ChangeMetric == StepChangeMetric.Default) { - countOnlyMovingIfGreenOnSegment = countOnlyMovingIfGreen; - if (countOnlyMovingIfGreenOnSegment) { - Constants.ServiceFactory.NetService.ProcessSegment(sourceSegmentId, delegate (ushort srcSegId, ref NetSegment segment) { - if (restrMan.IsRailSegment(segment.Info)) { - countOnlyMovingIfGreenOnSegment = false; - } - return true; - }); - } - - movingVehiclesMetrics = countOnlyMovingIfGreenOnSegment ? sourceSegmentEnd.MeasureOutgoingVehicles(false, debug) : null; - } - IDictionary[] allVehiclesMetrics = sourceSegmentEnd.MeasureOutgoingVehicles(true, debug); - - ExtVehicleType?[] vehTypeByLaneIndex = segLights.VehicleTypeByLaneIndex; + continue; + } + + // one of the traffic lights at this segment is green: count minimum traffic flowing through + ISegmentEnd sourceSegmentEnd = endMan.GetSegmentEnd(sourceSegmentId, segLights.StartNode); + if (sourceSegmentEnd == null) { + Log.Error($"TimedTrafficLightsStep.calcWaitFlow: No segment end @ seg. {sourceSegmentId} found!"); + continue; // skip invalid segment + } + + IDictionary[] movingVehiclesMetrics = null; + bool countOnlyMovingIfGreenOnSegment = false; + if (ChangeMetric == StepChangeMetric.Default) { + countOnlyMovingIfGreenOnSegment = countOnlyMovingIfGreen; + if (countOnlyMovingIfGreenOnSegment) { + Constants.ServiceFactory.NetService.ProcessSegment(sourceSegmentId, delegate (ushort srcSegId, ref NetSegment segment) { + if (restrMan.IsRailSegment(segment.Info)) { + countOnlyMovingIfGreenOnSegment = false; + } + return true; + }); + } + + movingVehiclesMetrics = countOnlyMovingIfGreenOnSegment ? sourceSegmentEnd.MeasureOutgoingVehicles(false, debug) : null; + } + IDictionary[] allVehiclesMetrics = sourceSegmentEnd.MeasureOutgoingVehicles(true, debug); + + var vehTypeByLaneIndex = segLights.VehicleTypeByLaneIndex; #if DEBUGTTL - if (debug) { - Log._Debug($"calcWaitFlow: Seg. {sourceSegmentId} @ {timedNodeId}, vehTypeByLaneIndex={string.Join(", ", vehTypeByLaneIndex.Select(x => x == null ? "null" : x.ToString()).ToArray())}"); - } + if (debug) { + Log._Debug($"calcWaitFlow: Seg. {sourceSegmentId} @ {timedNodeId}, vehTypeByLaneIndex={string.Join(", ", vehTypeByLaneIndex.Select(x => x == null ? "null" : x.ToString()).ToArray())}"); + } #endif - uint numSegFlows = 0; - uint numSegWaits = 0; - float curTotalSegFlow = 0; - float curTotalSegWait = 0; - // loop over source lanes - for (byte laneIndex = 0; laneIndex < vehTypeByLaneIndex.Length; ++laneIndex) { - ExtVehicleType? vehicleType = vehTypeByLaneIndex[laneIndex]; - if (vehicleType == null) { - continue; - } - - ICustomSegmentLight segLight = segLights.GetCustomLight(laneIndex); - if (segLight == null) { + uint numSegFlows = 0; + uint numSegWaits = 0; + float curTotalSegFlow = 0; + float curTotalSegWait = 0; + // loop over source lanes + for (byte laneIndex = 0; laneIndex < vehTypeByLaneIndex.Length; ++laneIndex) { + var vehicleType = vehTypeByLaneIndex[laneIndex]; + if (vehicleType == null) { + continue; + } + + ICustomSegmentLight segLight = segLights.GetCustomLight(laneIndex); + if (segLight == null) { #if DEBUGTTL - Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {sourceSegmentId}, node {timedNode.NodeId}!"); + Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {sourceSegmentId}, node {timedNode.NodeId}!"); #endif - continue; - } + continue; + } - IDictionary movingVehiclesMetric = countOnlyMovingIfGreenOnSegment ? movingVehiclesMetrics[laneIndex] : null; - IDictionary allVehiclesMetric = allVehiclesMetrics[laneIndex]; - if (allVehiclesMetrics == null) { + IDictionary movingVehiclesMetric = countOnlyMovingIfGreenOnSegment ? movingVehiclesMetrics[laneIndex] : null; + IDictionary allVehiclesMetric = allVehiclesMetrics[laneIndex]; + if (allVehiclesMetrics == null) { #if DEBUGTTL - if (debug) { - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: No cars on lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); - } + if (debug) { + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: No cars on lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); + } #endif - continue; - } + continue; + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Checking lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Checking lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); #endif - // loop over target segment: calculate waiting/moving traffic - uint numLaneFlows = 0; - uint numLaneWaits = 0; - uint curTotalLaneFlow = 0; - uint curTotalLaneWait = 0; - foreach (KeyValuePair f in allVehiclesMetric) { - ushort targetSegmentId = f.Key; - uint numVehicles = f.Value; + // loop over target segment: calculate waiting/moving traffic + uint numLaneFlows = 0; + uint numLaneWaits = 0; + uint curTotalLaneFlow = 0; + uint curTotalLaneWait = 0; + foreach (KeyValuePair f in allVehiclesMetric) { + ushort targetSegmentId = f.Key; + uint numVehicles = f.Value; - ArrowDirection dir; - if (!directions.TryGetValue(targetSegmentId, out dir)) { - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Direction undefined for target segment {targetSegmentId} @ {timedNodeId}"); - continue; - } + ArrowDirection dir; + if (!directions.TryGetValue(targetSegmentId, out dir)) { + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Direction undefined for target segment {targetSegmentId} @ {timedNodeId}"); + continue; + } - uint numMovingVehicles = countOnlyMovingIfGreenOnSegment ? movingVehiclesMetric[f.Key] : numVehicles; + uint numMovingVehicles = countOnlyMovingIfGreenOnSegment ? movingVehiclesMetric[f.Key] : numVehicles; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Total num of cars on seg. {sourceSegmentId}, lane {laneIndex} going to seg. {targetSegmentId}: {numMovingVehicles} (all: {numVehicles})"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Total num of cars on seg. {sourceSegmentId}, lane {laneIndex} going to seg. {targetSegmentId}: {numMovingVehicles} (all: {numVehicles})"); #endif - bool addToFlow = false; - switch (dir) { - case ArrowDirection.Turn: - addToFlow = Constants.ServiceFactory.SimulationService.LeftHandDrive ? segLight.IsRightGreen() : segLight.IsLeftGreen(); - break; - case ArrowDirection.Left: - addToFlow = segLight.IsLeftGreen(); - break; - case ArrowDirection.Right: - addToFlow = segLight.IsRightGreen(); - break; - case ArrowDirection.Forward: - default: - addToFlow = segLight.IsMainGreen(); - break; - } - - if (addToFlow) { - curTotalLaneFlow += numMovingVehicles; - ++numLaneFlows; + bool addToFlow = false; + switch (dir) { + case ArrowDirection.Turn: + addToFlow = Constants.ServiceFactory.SimulationService.LeftHandDrive ? segLight.IsRightGreen() : segLight.IsLeftGreen(); + break; + case ArrowDirection.Left: + addToFlow = segLight.IsLeftGreen(); + break; + case ArrowDirection.Right: + addToFlow = segLight.IsRightGreen(); + break; + case ArrowDirection.Forward: + default: + addToFlow = segLight.IsMainGreen(); + break; + } + + if (addToFlow) { + curTotalLaneFlow += numMovingVehicles; + ++numLaneFlows; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ## Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: COUTING as FLOWING -- numMovingVehicles={numMovingVehicles}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ## Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: COUTING as FLOWING -- numMovingVehicles={numMovingVehicles}"); #endif - } else { - curTotalLaneWait += numVehicles; - ++numLaneWaits; + } else { + curTotalLaneWait += numVehicles; + ++numLaneWaits; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ## Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: COUTING as WAITING -- numVehicles={numVehicles}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ## Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: COUTING as WAITING -- numVehicles={numVehicles}"); #endif - } + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>>>> Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: curTotalLaneFlow={curTotalLaneFlow}, curTotalLaneWait={curTotalLaneWait}, numLaneFlows={numLaneFlows}, numLaneWaits={numLaneWaits}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>>>> Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: curTotalLaneFlow={curTotalLaneFlow}, curTotalLaneWait={curTotalLaneWait}, numLaneFlows={numLaneFlows}, numLaneWaits={numLaneWaits}"); #endif - } // foreach target segment - - float meanLaneFlow = 0; - if (numLaneFlows > 0) { - switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { - case FlowWaitCalcMode.Mean: - default: - ++numSegFlows; - meanLaneFlow = (float)curTotalLaneFlow / (float)numLaneFlows; - curTotalSegFlow += meanLaneFlow; - break; - case FlowWaitCalcMode.Total: - numSegFlows += numLaneFlows; - curTotalSegFlow += curTotalLaneFlow; - break; - } - } - - float meanLaneWait = 0; - if (numLaneWaits > 0) { - switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { - case FlowWaitCalcMode.Mean: - default: - ++numSegWaits; - meanLaneWait = (float)curTotalLaneWait / (float)numLaneWaits; - curTotalSegWait += meanLaneWait; - break; - case FlowWaitCalcMode.Total: - numSegWaits += numLaneWaits; - curTotalSegWait += curTotalLaneWait; - break; - } - } + } // foreach target segment + + float meanLaneFlow = 0; + if (numLaneFlows > 0) { + switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { + case FlowWaitCalcMode.Mean: + default: + ++numSegFlows; + meanLaneFlow = (float)curTotalLaneFlow / (float)numLaneFlows; + curTotalSegFlow += meanLaneFlow; + break; + case FlowWaitCalcMode.Total: + numSegFlows += numLaneFlows; + curTotalSegFlow += curTotalLaneFlow; + break; + } + } + + float meanLaneWait = 0; + if (numLaneWaits > 0) { + switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { + case FlowWaitCalcMode.Mean: + default: + ++numSegWaits; + meanLaneWait = (float)curTotalLaneWait / (float)numLaneWaits; + curTotalSegWait += meanLaneWait; + break; + case FlowWaitCalcMode.Total: + numSegWaits += numLaneWaits; + curTotalSegWait += curTotalLaneWait; + break; + } + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>>> Vehicles @ lane {laneIndex}, seg. {sourceSegmentId}: meanLaneFlow={meanLaneFlow}, meanLaneWait={meanLaneWait} // curTotalSegFlow={curTotalSegFlow}, curTotalSegWait={curTotalSegWait}, numSegFlows={numSegFlows}, numSegWaits={numSegWaits}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>>> Vehicles @ lane {laneIndex}, seg. {sourceSegmentId}: meanLaneFlow={meanLaneFlow}, meanLaneWait={meanLaneWait} // curTotalSegFlow={curTotalSegFlow}, curTotalSegWait={curTotalSegWait}, numSegFlows={numSegFlows}, numSegWaits={numSegWaits}"); #endif - } // foreach source lane - - float meanSegFlow = 0; - if (numSegFlows > 0) { - switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { - case FlowWaitCalcMode.Mean: - default: - ++numNodeFlows; - meanSegFlow = (float)curTotalSegFlow / (float)numSegFlows; - curTotalNodeFlow += meanSegFlow; - break; - case FlowWaitCalcMode.Total: - numNodeFlows += numSegFlows; - curTotalNodeFlow += curTotalSegFlow; - break; - } - } - - float meanSegWait = 0; - if (numSegWaits > 0) { - switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { - case FlowWaitCalcMode.Mean: - default: - ++numNodeWaits; - meanSegWait = (float)curTotalSegWait / (float)numSegWaits; - curTotalNodeWait += meanSegWait; - break; - case FlowWaitCalcMode.Total: - numNodeWaits += numSegWaits; - curTotalNodeWait += curTotalSegWait; - break; - } - } + } // foreach source lane + + float meanSegFlow = 0; + if (numSegFlows > 0) { + switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { + case FlowWaitCalcMode.Mean: + default: + ++numNodeFlows; + meanSegFlow = (float)curTotalSegFlow / (float)numSegFlows; + curTotalNodeFlow += meanSegFlow; + break; + case FlowWaitCalcMode.Total: + numNodeFlows += numSegFlows; + curTotalNodeFlow += curTotalSegFlow; + break; + } + } + + float meanSegWait = 0; + if (numSegWaits > 0) { + switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { + case FlowWaitCalcMode.Mean: + default: + ++numNodeWaits; + meanSegWait = (float)curTotalSegWait / (float)numSegWaits; + curTotalNodeWait += meanSegWait; + break; + case FlowWaitCalcMode.Total: + numNodeWaits += numSegWaits; + curTotalNodeWait += curTotalSegWait; + break; + } + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>> Vehicles @ seg. {sourceSegmentId}: meanSegFlow={meanSegFlow}, meanSegWait={meanSegWait} // curTotalNodeFlow={curTotalNodeFlow}, curTotalNodeWait={curTotalNodeWait}, numNodeFlows={numNodeFlows}, numNodeWaits={numNodeWaits}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>> Vehicles @ seg. {sourceSegmentId}: meanSegFlow={meanSegFlow}, meanSegWait={meanSegWait} // curTotalNodeFlow={curTotalNodeFlow}, curTotalNodeWait={curTotalNodeWait}, numNodeFlows={numNodeFlows}, numNodeWaits={numNodeWaits}"); #endif - } // foreach source segment - - float meanNodeFlow = 0; - if (numNodeFlows > 0) { - switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { - case FlowWaitCalcMode.Mean: - default: - ++numFlows; - meanNodeFlow = (float)curTotalNodeFlow / (float)numNodeFlows; - curTotalFlow += meanNodeFlow; - break; - case FlowWaitCalcMode.Total: - numFlows += numNodeFlows; - curTotalFlow += curTotalNodeFlow; - break; - } - } - - float meanNodeWait = 0; - if (numNodeWaits > 0) { - switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { - case FlowWaitCalcMode.Mean: - default: - ++numWaits; - meanNodeWait = (float)curTotalNodeWait / (float)numNodeWaits; - curTotalWait += meanNodeWait; - break; - case FlowWaitCalcMode.Total: - numWaits += numNodeWaits; - curTotalWait += curTotalNodeWait; - break; - } - } + } // foreach source segment + + float meanNodeFlow = 0; + if (numNodeFlows > 0) { + switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { + case FlowWaitCalcMode.Mean: + default: + ++numFlows; + meanNodeFlow = (float)curTotalNodeFlow / (float)numNodeFlows; + curTotalFlow += meanNodeFlow; + break; + case FlowWaitCalcMode.Total: + numFlows += numNodeFlows; + curTotalFlow += curTotalNodeFlow; + break; + } + } + + float meanNodeWait = 0; + if (numNodeWaits > 0) { + switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { + case FlowWaitCalcMode.Mean: + default: + ++numWaits; + meanNodeWait = (float)curTotalNodeWait / (float)numNodeWaits; + curTotalWait += meanNodeWait; + break; + case FlowWaitCalcMode.Total: + numWaits += numNodeWaits; + curTotalWait += curTotalNodeWait; + break; + } + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Calculated flow for source node {timedNodeId}: meanNodeFlow={meanNodeFlow} meanNodeWait={meanNodeWait} // curTotalFlow={curTotalFlow}, curTotalWait={curTotalWait}, numFlows={numFlows}, numWaits={numWaits}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Calculated flow for source node {timedNodeId}: meanNodeFlow={meanNodeFlow} meanNodeWait={meanNodeWait} // curTotalFlow={curTotalFlow}, curTotalWait={curTotalWait}, numFlows={numFlows}, numWaits={numWaits}"); #endif - } // foreach timed node + } // foreach timed node - float meanFlow = numFlows > 0 ? (float)curTotalFlow / (float)numFlows : 0; - float meanWait = numWaits > 0 ? (float)curTotalWait / (float)numWaits : 0; - meanFlow /= WaitFlowBalance; // a value smaller than 1 rewards steady traffic currents + float meanFlow = numFlows > 0 ? (float)curTotalFlow / (float)numFlows : 0; + float meanWait = numWaits > 0 ? (float)curTotalWait / (float)numWaits : 0; + meanFlow /= WaitFlowBalance; // a value smaller than 1 rewards steady traffic currents - wait = (float)meanWait; - flow = meanFlow; + wait = (float)meanWait; + flow = meanFlow; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ***CALCULATION FINISHED*** for master node {timedNode.NodeId}: flow={flow} wait={wait}"); + if (debug) + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ***CALCULATION FINISHED*** for master node {timedNode.NodeId}: flow={flow} wait={wait}"); #endif - } - - internal void ChangeLightMode(ushort segmentId, ExtVehicleType vehicleType, LightMode mode) { - ICustomSegmentLight light = CustomSegmentLights[segmentId].GetCustomLight(vehicleType); - if (light != null) { - light.CurrentMode = mode; - } - } - - public ICustomSegmentLights RemoveSegmentLights(ushort segmentId) { + } + + internal void ChangeLightMode(ushort segmentId, + API.Traffic.Enums.ExtVehicleType vehicleType, + LightMode mode) { + ICustomSegmentLight light = CustomSegmentLights[segmentId].GetCustomLight(vehicleType); + if (light != null) { + light.CurrentMode = mode; + } + } + + public ICustomSegmentLights RemoveSegmentLights(ushort segmentId) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.RemoveSegmentLights({segmentId}) called."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.RemoveSegmentLights({segmentId}) called."); #endif - ICustomSegmentLights ret = null; - if (CustomSegmentLights.TryGetValue(segmentId, out ret)) { - CustomSegmentLights.Remove(segmentId); - } - return ret; - } + ICustomSegmentLights ret = null; + if (CustomSegmentLights.TryGetValue(segmentId, out ret)) { + CustomSegmentLights.Remove(segmentId); + } + return ret; + } - public ICustomSegmentLights GetSegmentLights(ushort segmentId) { + public ICustomSegmentLights GetSegmentLights(ushort segmentId) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.GetSegmentLights({segmentId}) called."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.GetSegmentLights({segmentId}) called."); #endif - return GetSegmentLights(timedNode.NodeId, segmentId); - } + return GetSegmentLights(timedNode.NodeId, segmentId); + } - public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId) { + public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}) called."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}) called."); #endif - if (nodeId != timedNode.NodeId) { - Log.Warning($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}): TTL @ node {timedNode.NodeId} does not handle custom traffic lights for node {nodeId}"); - return null; - } - - ICustomSegmentLights customLights; - if (CustomSegmentLights.TryGetValue(segmentId, out customLights)) { - return customLights; - } else { - Log.Info($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}): TTL @ node {timedNode.NodeId} does not know segment {segmentId}"); - return null; - } - } - - public bool RelocateSegmentLights(ushort sourceSegmentId, ushort targetSegmentId) { + if (nodeId != timedNode.NodeId) { + Log.Warning($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}): TTL @ node {timedNode.NodeId} does not handle custom traffic lights for node {nodeId}"); + return null; + } + + ICustomSegmentLights customLights; + if (CustomSegmentLights.TryGetValue(segmentId, out customLights)) { + return customLights; + } else { + Log.Info($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}): TTL @ node {timedNode.NodeId} does not know segment {segmentId}"); + return null; + } + } + + public bool RelocateSegmentLights(ushort sourceSegmentId, ushort targetSegmentId) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}) called."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}) called."); #endif - ICustomSegmentLights sourceLights = null; - if (! CustomSegmentLights.TryGetValue(sourceSegmentId, out sourceLights)) { - Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Timed traffic light does not know source segment {sourceSegmentId}. Cannot relocate to {targetSegmentId}."); - return false; - } - - if (! Constants.ServiceFactory.NetService.IsSegmentValid(targetSegmentId)) { - Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Target segment {targetSegmentId} is invalid"); - return false; - } - - bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, timedNode.NodeId); - if (startNode == null) { - Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Node {timedNode.NodeId} is neither start nor end node of target segment {targetSegmentId}"); - return false; - } - - CustomSegmentLights.Remove(sourceSegmentId); - Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(targetSegmentId, (bool)startNode).Housekeeping(true, true); - sourceLights.Relocate(targetSegmentId, (bool)startNode, this); - CustomSegmentLights[targetSegmentId] = sourceLights; - - Log._Debug($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Relocated lights: {sourceSegmentId} -> {targetSegmentId} @ node {timedNode.NodeId}"); - return true; - } - - /// - /// Adds a new segment to this step. It is cloned from the live custom traffic light. - /// - /// - internal bool AddSegment(ushort segmentId, bool startNode, bool makeRed) { + ICustomSegmentLights sourceLights = null; + if (! CustomSegmentLights.TryGetValue(sourceSegmentId, out sourceLights)) { + Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Timed traffic light does not know source segment {sourceSegmentId}. Cannot relocate to {targetSegmentId}."); + return false; + } + + if (! Constants.ServiceFactory.NetService.IsSegmentValid(targetSegmentId)) { + Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Target segment {targetSegmentId} is invalid"); + return false; + } + + bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, timedNode.NodeId); + if (startNode == null) { + Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Node {timedNode.NodeId} is neither start nor end node of target segment {targetSegmentId}"); + return false; + } + + CustomSegmentLights.Remove(sourceSegmentId); + Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(targetSegmentId, (bool)startNode).Housekeeping(true, true); + sourceLights.Relocate(targetSegmentId, (bool)startNode, this); + CustomSegmentLights[targetSegmentId] = sourceLights; + + Log._Debug($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Relocated lights: {sourceSegmentId} -> {targetSegmentId} @ node {timedNode.NodeId}"); + return true; + } + + /// + /// Adds a new segment to this step. It is cloned from the live custom traffic light. + /// + /// + internal bool AddSegment(ushort segmentId, bool startNode, bool makeRed) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}) called @ node {timedNode.NodeId}."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}) called @ node {timedNode.NodeId}."); #endif - if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { - Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is invalid"); - return false; - } + if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is invalid"); + return false; + } - if (Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, startNode) != timedNode.NodeId) { - Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is not connected to node {timedNode.NodeId} @ start {startNode}"); - return false; - } + if (Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, startNode) != timedNode.NodeId) { + Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is not connected to node {timedNode.NodeId} @ start {startNode}"); + return false; + } - ICustomSegmentLightsManager customSegLightsMan = Constants.ManagerFactory.CustomSegmentLightsManager; + ICustomSegmentLightsManager customSegLightsMan = Constants.ManagerFactory.CustomSegmentLightsManager; - ICustomSegmentLights liveLights = customSegLightsMan.GetOrLiveSegmentLights(segmentId, startNode); - liveLights.Housekeeping(true, true); + ICustomSegmentLights liveLights = customSegLightsMan.GetOrLiveSegmentLights(segmentId, startNode); + liveLights.Housekeeping(true, true); - ICustomSegmentLights clonedLights = liveLights.Clone(this); + ICustomSegmentLights clonedLights = liveLights.Clone(this); - CustomSegmentLights.Add(segmentId, clonedLights); - if (makeRed) - CustomSegmentLights[segmentId].MakeRed(); - else - CustomSegmentLights[segmentId].MakeRedOrGreen(); - return customSegLightsMan.ApplyLightModes(segmentId, startNode, clonedLights); - } + CustomSegmentLights.Add(segmentId, clonedLights); + if (makeRed) + CustomSegmentLights[segmentId].MakeRed(); + else + CustomSegmentLights[segmentId].MakeRedOrGreen(); + return customSegLightsMan.ApplyLightModes(segmentId, startNode, clonedLights); + } - public bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights) { + public bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.SetSegmentLights({nodeId}, {segmentId}, {lights}) called."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.SetSegmentLights({nodeId}, {segmentId}, {lights}) called."); #endif - if (nodeId != timedNode.NodeId) { - Log.Warning($"TimedTrafficLightsStep.SetSegmentLights({nodeId}, {segmentId}, {lights}): TTL @ node {timedNode.NodeId} does not handle custom traffic lights for node {nodeId}"); - return false; - } + if (nodeId != timedNode.NodeId) { + Log.Warning($"TimedTrafficLightsStep.SetSegmentLights({nodeId}, {segmentId}, {lights}): TTL @ node {timedNode.NodeId} does not handle custom traffic lights for node {nodeId}"); + return false; + } - return SetSegmentLights(segmentId, lights); - } + return SetSegmentLights(segmentId, lights); + } - public bool SetSegmentLights(ushort segmentId, ICustomSegmentLights lights) { + public bool SetSegmentLights(ushort segmentId, ICustomSegmentLights lights) { #if DEBUGTTL - if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) - Log._Debug($"TimedTrafficLightsStep.SetSegmentLights({segmentId}, {lights}) called."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.SetSegmentLights({segmentId}, {lights}) called."); #endif - if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { - Log.Error($"TimedTrafficLightsStep.SetSegmentLights({segmentId}): Segment {segmentId} is invalid"); - return false; - } - - bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); - if (startNode == null) { - Log.Error($"TimedTrafficLightsStep.SetSegmentLights: Segment {segmentId} is not connected to node {timedNode.NodeId}"); - return false; - } - - Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(segmentId, (bool)startNode).Housekeeping(true, true); - lights.Relocate(segmentId, (bool)startNode, this); - CustomSegmentLights[segmentId] = lights; - Log._Debug($"TimedTrafficLightsStep.SetSegmentLights: Set lights @ seg. {segmentId}, node {timedNode.NodeId}"); - return true; - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add = true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public void SetLightMode(ushort segmentId, bool startNode, ExtVehicleType vehicleType, LightMode mode) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public void AddNodeLights(ushort nodeId) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public void RemoveNodeLights(ushort nodeId) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - void ICustomSegmentLightsManager.RemoveSegmentLights(ushort segmentId) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public void RemoveSegmentLight(ushort segmentId, bool startNode) { - throw new NotImplementedException(); - } - - // TODO IMPROVE THIS! Liskov substitution principle must hold. - public bool IsSegmentLight(ushort segmentId, bool startNode) { - throw new NotImplementedException(); - } - } -} + if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + Log.Error($"TimedTrafficLightsStep.SetSegmentLights({segmentId}): Segment {segmentId} is invalid"); + return false; + } + + bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); + if (startNode == null) { + Log.Error($"TimedTrafficLightsStep.SetSegmentLights: Segment {segmentId} is not connected to node {timedNode.NodeId}"); + return false; + } + + Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(segmentId, (bool)startNode).Housekeeping(true, true); + lights.Relocate(segmentId, (bool)startNode, this); + CustomSegmentLights[segmentId] = lights; + Log._Debug($"TimedTrafficLightsStep.SetSegmentLights: Set lights @ seg. {segmentId}, node {timedNode.NodeId}"); + return true; + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add = true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public void SetLightMode(ushort segmentId, + bool startNode, + API.Traffic.Enums.ExtVehicleType vehicleType, + LightMode mode) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public void AddNodeLights(ushort nodeId) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public void RemoveNodeLights(ushort nodeId) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + void ICustomSegmentLightsManager.RemoveSegmentLights(ushort segmentId) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public void RemoveSegmentLight(ushort segmentId, bool startNode) { + throw new NotImplementedException(); + } + + // TODO IMPROVE THIS! Liskov substitution principle must hold. + public bool IsSegmentLight(ushort segmentId, bool startNode) { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/LaneArrowTool.cs b/TLM/TLM/UI/SubTools/LaneArrowTool.cs index dd99df9e7..b161f9baf 100644 --- a/TLM/TLM/UI/SubTools/LaneArrowTool.cs +++ b/TLM/TLM/UI/SubTools/LaneArrowTool.cs @@ -19,6 +19,8 @@ using UnityEngine; namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + public class LaneArrowTool : SubTool { private bool _cursorInSecondaryPanel; diff --git a/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs index 2a42027a7..cdf479531 100644 --- a/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs +++ b/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs @@ -1,704 +1,694 @@ -using ColossalFramework; -using ColossalFramework.Math; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Custom.AI; -using TrafficManager.State; -using TrafficManager.Geometry; -using TrafficManager.TrafficLight; -using UnityEngine; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using TrafficManager.Manager.Impl; -using TrafficManager.Geometry.Impl; -using ColossalFramework.UI; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -namespace TrafficManager.UI.SubTools { - public class ManualTrafficLightsTool : SubTool { - private readonly int[] _hoveredButton = new int[2]; - private readonly GUIStyle _counterStyle = new GUIStyle(); - - public ManualTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { - - } - - public override void OnSecondaryClickOverlay() { - if (IsCursorInPanel()) - return; - Cleanup(); - SelectedNodeId = 0; - } - - public override void OnPrimaryClickOverlay() { - if (IsCursorInPanel()) - return; - if (SelectedNodeId != 0) return; - - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; - - if (!tlsMan.TrafficLightSimulations[HoveredNodeId].IsTimedLight()) { - if ((Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_flags & NetNode.Flags.TrafficLights) == NetNode.Flags.None) { - prioMan.RemovePrioritySignsFromNode(HoveredNodeId); - TrafficLightManager.Instance.AddTrafficLight(HoveredNodeId, ref Singleton.instance.m_nodes.m_buffer[HoveredNodeId]); - } - - if (tlsMan.SetUpManualTrafficLight(HoveredNodeId)) { - SelectedNodeId = HoveredNodeId; - } - - /*for (var s = 0; s < 8; s++) { - var segment = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].GetSegment(s); - if (segment != 0 && !TrafficPriority.IsPrioritySegment(SelectedNodeId, segment)) { - TrafficPriority.AddPrioritySegment(SelectedNodeId, segment, SegmentEnd.PriorityType.None); - } - }*/ - } else { - MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); - } - } - - public override void OnToolGUI(Event e) { - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - var hoveredSegment = false; - - if (SelectedNodeId != 0) { - CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - JunctionRestrictionsManager junctionRestrictionsManager = JunctionRestrictionsManager.Instance; - - if (!tlsMan.HasManualSimulation(SelectedNodeId)) { - return; - } - tlsMan.TrafficLightSimulations[SelectedNodeId].Housekeeping(); - - /*if (Singleton.instance.m_nodes.m_buffer[SelectedNode].CountSegments() == 2) { - _guiManualTrafficLightsCrosswalk(ref Singleton.instance.m_nodes.m_buffer[SelectedNode]); - return; - }*/ // TODO check - - for (int i = 0; i < 8; ++i) { - ushort segmentId = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].GetSegment(i); - if (segmentId == 0) { - continue; - } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, SelectedNodeId); - - var position = CalculateNodePositionForSegment(Singleton.instance.m_nodes.m_buffer[SelectedNodeId], ref Singleton.instance.m_segments.m_buffer[segmentId]); - var segmentLights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode, false); - if (segmentLights == null) - continue; - - bool showPedLight = segmentLights.PedestrianLightState != null && junctionRestrictionsManager.IsPedestrianCrossingAllowed(segmentLights.SegmentId, segmentLights.StartNode); - - Vector3 screenPos; - bool visible = MainTool.WorldToScreenPoint(position, out screenPos); - - if (!visible) - continue; - - var diff = position - Camera.main.transform.position; - var zoom = 1.0f / diff.magnitude * 100f; - - // original / 2.5 - var lightWidth = 41f * zoom; - var lightHeight = 97f * zoom; - - var pedestrianWidth = 36f * zoom; - var pedestrianHeight = 61f * zoom; - - // SWITCH MODE BUTTON - var modeWidth = 41f * zoom; - var modeHeight = 38f * zoom; - - var guiColor = GUI.color; - - if (showPedLight) { - // pedestrian light - - // SWITCH MANUAL PEDESTRIAN LIGHT BUTTON - hoveredSegment = RenderManualPedestrianLightSwitch(zoom, segmentId, screenPos, lightWidth, segmentLights, hoveredSegment); - - // SWITCH PEDESTRIAN LIGHT - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == 2 && segmentLights.ManualPedestrianMode); - GUI.color = guiColor; - - var myRect3 = new Rect(screenPos.x - pedestrianWidth / 2 - lightWidth + 5f * zoom, screenPos.y - pedestrianHeight / 2 + 22f * zoom, pedestrianWidth, pedestrianHeight); - - switch (segmentLights.PedestrianLightState) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect3, TextureResources.PedestrianGreenLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(myRect3, TextureResources.PedestrianRedLightTexture2D); - break; - } - - hoveredSegment = IsPedestrianLightHovered(myRect3, segmentId, hoveredSegment, segmentLights); - } - - int lightOffset = -1; - foreach (ExtVehicleType vehicleType in segmentLights.VehicleTypes) { - ++lightOffset; - ICustomSegmentLight segmentLight = segmentLights.GetCustomLight(vehicleType); - - Vector3 offsetScreenPos = screenPos; - offsetScreenPos.y -= (lightHeight + 10f * zoom) * lightOffset; - - SetAlpha(segmentId, -1); - - var myRect1 = new Rect(offsetScreenPos.x - modeWidth / 2, offsetScreenPos.y - modeHeight / 2 + modeHeight - 7f * zoom, modeWidth, modeHeight); - - GUI.DrawTexture(myRect1, TextureResources.LightModeTexture2D); - - hoveredSegment = GetHoveredSegment(myRect1, segmentId, hoveredSegment, segmentLight); - - // COUNTER - hoveredSegment = RenderCounter(segmentId, offsetScreenPos, modeWidth, modeHeight, zoom, segmentLights, hoveredSegment); - - if (vehicleType != ExtVehicleType.None) { - // Info sign - var infoWidth = 56.125f * zoom; - var infoHeight = 51.375f * zoom; - - int numInfos = 0; - for (int k = 0; k < TrafficManagerTool.InfoSignsToDisplay.Length; ++k) { - if ((TrafficManagerTool.InfoSignsToDisplay[k] & vehicleType) == ExtVehicleType.None) - continue; - var infoRect = new Rect(offsetScreenPos.x + modeWidth / 2f + 7f * zoom * (float)(numInfos + 1) + infoWidth * (float)numInfos, offsetScreenPos.y - infoHeight / 2f, infoWidth, infoHeight); - guiColor.a = MainTool.GetHandleAlpha(false); - GUI.DrawTexture(infoRect, TextureResources.VehicleInfoSignTextures[TrafficManagerTool.InfoSignsToDisplay[k]]); - ++numInfos; - } - } - - ExtSegment seg = segMan.ExtSegments[segmentId]; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; - if (seg.oneWay && segEnd.outgoing) continue; - - bool hasLeftSegment; - bool hasForwardSegment; - bool hasRightSegment; - segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[segmentId], out hasLeftSegment, out hasForwardSegment, out hasRightSegment); - - switch (segmentLight.CurrentMode) { - case LightMode.Simple: - hoveredSegment = SimpleManualSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); - break; - case LightMode.SingleLeft: - hoveredSegment = LeftForwardRManualSegmentLightMode(hasLeftSegment, segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment, hasForwardSegment, hasRightSegment); - break; - case LightMode.SingleRight: - hoveredSegment = RightForwardLSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, hasForwardSegment, hasLeftSegment, segmentLight, hasRightSegment, hoveredSegment); - break; - default: - // left arrow light - if (hasLeftSegment) - hoveredSegment = LeftArrowLightMode(segmentId, lightWidth, hasRightSegment, hasForwardSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); - - // forward arrow light - if (hasForwardSegment) - hoveredSegment = ForwardArrowLightMode(segmentId, lightWidth, hasRightSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); - - // right arrow light - if (hasRightSegment) - hoveredSegment = RightArrowLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); - break; - } - } - } - } - - if (hoveredSegment) return; - _hoveredButton[0] = 0; - _hoveredButton[1] = 0; - } - - public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { - if (SelectedNodeId != 0) { - RenderManualNodeOverlays(cameraInfo); - } else { - RenderManualSelectionOverlay(cameraInfo); - } - } - - private bool RenderManualPedestrianLightSwitch(float zoom, int segmentId, Vector3 screenPos, float lightWidth, - ICustomSegmentLights segmentLights, bool hoveredSegment) { - if (segmentLights.PedestrianLightState == null) - return false; - - var guiColor = GUI.color; - var manualPedestrianWidth = 36f * zoom; - var manualPedestrianHeight = 35f * zoom; - - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && (_hoveredButton[1] == 1 || _hoveredButton[1] == 2)); - - GUI.color = guiColor; - - var myRect2 = new Rect(screenPos.x - manualPedestrianWidth / 2 - lightWidth + 5f * zoom, - screenPos.y - manualPedestrianHeight / 2 - 9f * zoom, manualPedestrianWidth, manualPedestrianHeight); - - GUI.DrawTexture(myRect2, segmentLights.ManualPedestrianMode ? TextureResources.PedestrianModeManualTexture2D : TextureResources.PedestrianModeAutomaticTexture2D); - - if (!myRect2.Contains(Event.current.mousePosition)) - return hoveredSegment; - - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 1; - - if (!MainTool.CheckClicked()) - return true; - - segmentLights.ManualPedestrianMode = !segmentLights.ManualPedestrianMode; - return true; - } - - private bool IsPedestrianLightHovered(Rect myRect3, int segmentId, bool hoveredSegment, ICustomSegmentLights segmentLights) { - if (!myRect3.Contains(Event.current.mousePosition)) - return hoveredSegment; - if (segmentLights.PedestrianLightState == null) - return false; - - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 2; - - if (!MainTool.CheckClicked()) - return true; - - if (!segmentLights.ManualPedestrianMode) { - segmentLights.ManualPedestrianMode = true; - } else { - segmentLights.ChangeLightPedestrian(); - } - return true; - } - - private bool GetHoveredSegment(Rect myRect1, int segmentId, bool hoveredSegment, ICustomSegmentLight segmentDict) { - if (!myRect1.Contains(Event.current.mousePosition)) - return hoveredSegment; - - //Log.Message("mouse in myRect1"); - _hoveredButton[0] = segmentId; - _hoveredButton[1] = -1; - - if (!MainTool.CheckClicked()) - return true; - segmentDict.ToggleMode(); - return true; - } - - private bool RenderCounter(int segmentId, Vector3 screenPos, float modeWidth, float modeHeight, float zoom, - ICustomSegmentLights segmentLights, bool hoveredSegment) { - SetAlpha(segmentId, 0); - - var myRectCounter = new Rect(screenPos.x - modeWidth / 2, screenPos.y - modeHeight / 2 - 6f * zoom, modeWidth, modeHeight); - - GUI.DrawTexture(myRectCounter, TextureResources.LightCounterTexture2D); - - var counterSize = 20f * zoom; - - var counter = segmentLights.LastChange(); - - var myRectCounterNum = new Rect(screenPos.x - counterSize + 15f * zoom + (counter >= 10 ? -5 * zoom : 0f), - screenPos.y - counterSize + 11f * zoom, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (!myRectCounter.Contains(Event.current.mousePosition)) - return hoveredSegment; - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 0; - return true; - } - - private bool SimpleManualSegmentLightMode(int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, - float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { - SetAlpha(segmentId, 3); - - var myRect4 = - new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - switch (segmentDict.LightMain) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect4, TextureResources.GreenLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect4, TextureResources.RedLightTexture2D); - break; - } - - if (!myRect4.Contains(Event.current.mousePosition)) - return hoveredSegment; - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 3; - - if (!MainTool.CheckClicked()) - return true; - segmentDict.ChangeMainLight(); - return true; - } - - private bool LeftForwardRManualSegmentLightMode(bool hasLeftSegment, int segmentId, Vector3 screenPos, float lightWidth, - float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment, - bool hasForwardSegment, bool hasRightSegment) { - if (hasLeftSegment) { - // left arrow light - SetAlpha(segmentId, 3); - - var myRect4 = - new Rect(screenPos.x - lightWidth / 2 - lightWidth * 2 - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - switch (segmentDict.LightLeft) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); - break; - } - - if (myRect4.Contains(Event.current.mousePosition)) { - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 3; - hoveredSegment = true; - - if (MainTool.CheckClicked()) { - segmentDict.ChangeLeftLight(); - } - } - } - - // forward-right arrow light - SetAlpha(segmentId, 4); - - var myRect5 = - new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - if (hasForwardSegment && hasRightSegment) { - switch (segmentDict.LightMain) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect5, TextureResources.GreenLightForwardRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect5, TextureResources.RedLightForwardRightTexture2D); - break; - } - } else if (!hasRightSegment) { - switch (segmentDict.LightMain) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect5, TextureResources.GreenLightStraightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect5, TextureResources.RedLightStraightTexture2D); - break; - } - } else { - switch (segmentDict.LightMain) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); - break; - } - } - - if (!myRect5.Contains(Event.current.mousePosition)) - return hoveredSegment; - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 4; - - if (!MainTool.CheckClicked()) - return true; - segmentDict.ChangeMainLight(); - return true; - } - - private bool RightForwardLSegmentLightMode(int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, - float zoom, float lightHeight, bool hasForwardSegment, bool hasLeftSegment, ICustomSegmentLight segmentDict, - bool hasRightSegment, bool hoveredSegment) { - SetAlpha(segmentId, 3); - - var myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth * 2 - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - if (hasForwardSegment && hasLeftSegment) { - switch (segmentDict.LightLeft) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect4, TextureResources.GreenLightForwardLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect4, TextureResources.RedLightForwardLeftTexture2D); - break; - } - } else if (!hasLeftSegment) { - if (!hasRightSegment) { - myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - } - - switch (segmentDict.LightMain) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect4, TextureResources.GreenLightStraightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect4, TextureResources.RedLightStraightTexture2D); - break; - } - } else { - if (!hasRightSegment) { - myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - } - - switch (segmentDict.LightMain) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); - break; - } - } - - - if (myRect4.Contains(Event.current.mousePosition)) { - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 3; - hoveredSegment = true; - - if (MainTool.CheckClicked()) { - segmentDict.ChangeMainLight(); - } - } - - var guiColor = GUI.color; - // right arrow light - if (hasRightSegment) - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == 4); - - GUI.color = guiColor; - - var myRect5 = - new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - switch (segmentDict.LightRight) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); - break; - } - - - if (!myRect5.Contains(Event.current.mousePosition)) - return hoveredSegment; - - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 4; - - if (!MainTool.CheckClicked()) - return true; - segmentDict.ChangeRightLight(); - return true; - } - - private bool LeftArrowLightMode(int segmentId, float lightWidth, bool hasRightSegment, - bool hasForwardSegment, Vector3 screenPos, float pedestrianWidth, float zoom, float lightHeight, - ICustomSegmentLight segmentDict, bool hoveredSegment) { - SetAlpha(segmentId, 3); - - var offsetLight = lightWidth; - - if (hasRightSegment) - offsetLight += lightWidth; - - if (hasForwardSegment) - offsetLight += lightWidth; - - var myRect4 = - new Rect(screenPos.x - lightWidth / 2 - offsetLight - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - switch (segmentDict.LightLeft) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); - break; - } - - if (!myRect4.Contains(Event.current.mousePosition)) - return hoveredSegment; - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 3; - - if (!MainTool.CheckClicked()) - return true; - segmentDict.ChangeLeftLight(); - - if (!hasForwardSegment) { - segmentDict.ChangeMainLight(); - } - return true; - } - - private bool ForwardArrowLightMode(int segmentId, float lightWidth, bool hasRightSegment, - Vector3 screenPos, float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, - bool hoveredSegment) { - SetAlpha(segmentId, 4); - - var offsetLight = lightWidth; - - if (hasRightSegment) - offsetLight += lightWidth; - - var myRect6 = - new Rect(screenPos.x - lightWidth / 2 - offsetLight - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - switch (segmentDict.LightMain) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect6, TextureResources.GreenLightStraightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect6, TextureResources.RedLightStraightTexture2D); - break; - } - - if (!myRect6.Contains(Event.current.mousePosition)) - return hoveredSegment; - - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 4; - - if (!MainTool.CheckClicked()) - return true; - segmentDict.ChangeMainLight(); - return true; - } - - private bool RightArrowLightMode(int segmentId, Vector3 screenPos, float lightWidth, - float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { - SetAlpha(segmentId, 5); - - var myRect5 = - new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, - screenPos.y - lightHeight / 2, lightWidth, lightHeight); - - switch (segmentDict.LightRight) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); - break; - } - - if (!myRect5.Contains(Event.current.mousePosition)) - return hoveredSegment; - - _hoveredButton[0] = segmentId; - _hoveredButton[1] = 5; - - if (!MainTool.CheckClicked()) - return true; - segmentDict.ChangeRightLight(); - return true; - } - - private Vector3 CalculateNodePositionForSegment(NetNode node, int segmentId) { - var position = node.m_position; - - var segment = Singleton.instance.m_segments.m_buffer[segmentId]; - if (segment.m_startNode == SelectedNodeId) { - position.x += segment.m_startDirection.x * 10f; - position.y += segment.m_startDirection.y * 10f; - position.z += segment.m_startDirection.z * 10f; - } else { - position.x += segment.m_endDirection.x * 10f; - position.y += segment.m_endDirection.y * 10f; - position.z += segment.m_endDirection.z * 10f; - } - return position; - } - - private Vector3 CalculateNodePositionForSegment(NetNode node, ref NetSegment segment) { - var position = node.m_position; - - const float offset = 25f; - - if (segment.m_startNode == SelectedNodeId) { - position.x += segment.m_startDirection.x * offset; - position.y += segment.m_startDirection.y * offset; - position.z += segment.m_startDirection.z * offset; - } else { - position.x += segment.m_endDirection.x * offset; - position.y += segment.m_endDirection.y * offset; - position.z += segment.m_endDirection.z * offset; - } - - return position; - } - - private void SetAlpha(int segmentId, int buttonId) { - var guiColor = GUI.color; - - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == buttonId); - - GUI.color = guiColor; - } - - private void RenderManualSelectionOverlay(RenderManager.CameraInfo cameraInfo) { - if (HoveredNodeId == 0) return; - - MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, false, false); - /*var segment = Singleton.instance.m_segments.m_buffer[Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_segment0]; - - //if ((node.m_flags & NetNode.Flags.TrafficLights) == NetNode.Flags.None) return; - Bezier3 bezier; - bezier.a = Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_position; - bezier.d = Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_position; - - var color = MainTool.GetToolColor(false, false); - - NetSegment.CalculateMiddlePoints(bezier.a, segment.m_startDirection, bezier.d, - segment.m_endDirection, false, false, out bezier.b, out bezier.c); - MainTool.DrawOverlayBezier(cameraInfo, bezier, color);*/ - } - - private void RenderManualNodeOverlays(RenderManager.CameraInfo cameraInfo) { - if (!TrafficLightSimulationManager.Instance.HasManualSimulation(SelectedNodeId)) { - return; - } - - MainTool.DrawNodeCircle(cameraInfo, SelectedNodeId, true, false); - } - - public override void Cleanup() { - if (SelectedNodeId == 0) return; - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - if (!tlsMan.HasManualSimulation(SelectedNodeId)) { - return; - } - - tlsMan.RemoveNodeFromSimulation(SelectedNodeId, true, false); - } - } -} +namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using Manager; + using Manager.Impl; + using Traffic.Data; + using TrafficLight; + using UnityEngine; + + public class ManualTrafficLightsTool : SubTool { + private readonly int[] _hoveredButton = new int[2]; + private readonly GUIStyle _counterStyle = new GUIStyle(); + + public ManualTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { + + } + + public override void OnSecondaryClickOverlay() { + if (IsCursorInPanel()) + return; + Cleanup(); + SelectedNodeId = 0; + } + + public override void OnPrimaryClickOverlay() { + if (IsCursorInPanel()) + return; + if (SelectedNodeId != 0) return; + + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; + + if (!tlsMan.TrafficLightSimulations[HoveredNodeId].IsTimedLight()) { + if ((Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_flags & NetNode.Flags.TrafficLights) == NetNode.Flags.None) { + prioMan.RemovePrioritySignsFromNode(HoveredNodeId); + TrafficLightManager.Instance.AddTrafficLight(HoveredNodeId, ref Singleton.instance.m_nodes.m_buffer[HoveredNodeId]); + } + + if (tlsMan.SetUpManualTrafficLight(HoveredNodeId)) { + SelectedNodeId = HoveredNodeId; + } + + /*for (var s = 0; s < 8; s++) { + var segment = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].GetSegment(s); + if (segment != 0 && !TrafficPriority.IsPrioritySegment(SelectedNodeId, segment)) { + TrafficPriority.AddPrioritySegment(SelectedNodeId, segment, SegmentEnd.PriorityType.None); + } + }*/ + } else { + MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); + } + } + + public override void OnToolGUI(Event e) { + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + var hoveredSegment = false; + + if (SelectedNodeId != 0) { + CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + JunctionRestrictionsManager junctionRestrictionsManager = JunctionRestrictionsManager.Instance; + + if (!tlsMan.HasManualSimulation(SelectedNodeId)) { + return; + } + tlsMan.TrafficLightSimulations[SelectedNodeId].Housekeeping(); + + /*if (Singleton.instance.m_nodes.m_buffer[SelectedNode].CountSegments() == 2) { + _guiManualTrafficLightsCrosswalk(ref Singleton.instance.m_nodes.m_buffer[SelectedNode]); + return; + }*/ // TODO check + + for (int i = 0; i < 8; ++i) { + ushort segmentId = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].GetSegment(i); + if (segmentId == 0) { + continue; + } + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, SelectedNodeId); + + var position = CalculateNodePositionForSegment(Singleton.instance.m_nodes.m_buffer[SelectedNodeId], ref Singleton.instance.m_segments.m_buffer[segmentId]); + var segmentLights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode, false); + if (segmentLights == null) + continue; + + bool showPedLight = segmentLights.PedestrianLightState != null && junctionRestrictionsManager.IsPedestrianCrossingAllowed(segmentLights.SegmentId, segmentLights.StartNode); + + Vector3 screenPos; + bool visible = MainTool.WorldToScreenPoint(position, out screenPos); + + if (!visible) + continue; + + var diff = position - Camera.main.transform.position; + var zoom = 1.0f / diff.magnitude * 100f; + + // original / 2.5 + var lightWidth = 41f * zoom; + var lightHeight = 97f * zoom; + + var pedestrianWidth = 36f * zoom; + var pedestrianHeight = 61f * zoom; + + // SWITCH MODE BUTTON + var modeWidth = 41f * zoom; + var modeHeight = 38f * zoom; + + var guiColor = GUI.color; + + if (showPedLight) { + // pedestrian light + + // SWITCH MANUAL PEDESTRIAN LIGHT BUTTON + hoveredSegment = RenderManualPedestrianLightSwitch(zoom, segmentId, screenPos, lightWidth, segmentLights, hoveredSegment); + + // SWITCH PEDESTRIAN LIGHT + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == 2 && segmentLights.ManualPedestrianMode); + GUI.color = guiColor; + + var myRect3 = new Rect(screenPos.x - pedestrianWidth / 2 - lightWidth + 5f * zoom, screenPos.y - pedestrianHeight / 2 + 22f * zoom, pedestrianWidth, pedestrianHeight); + + switch (segmentLights.PedestrianLightState) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect3, TextureResources.PedestrianGreenLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(myRect3, TextureResources.PedestrianRedLightTexture2D); + break; + } + + hoveredSegment = IsPedestrianLightHovered(myRect3, segmentId, hoveredSegment, segmentLights); + } + + int lightOffset = -1; + foreach (ExtVehicleType vehicleType in segmentLights.VehicleTypes) { + ++lightOffset; + ICustomSegmentLight segmentLight = segmentLights.GetCustomLight(vehicleType); + + Vector3 offsetScreenPos = screenPos; + offsetScreenPos.y -= (lightHeight + 10f * zoom) * lightOffset; + + SetAlpha(segmentId, -1); + + var myRect1 = new Rect(offsetScreenPos.x - modeWidth / 2, offsetScreenPos.y - modeHeight / 2 + modeHeight - 7f * zoom, modeWidth, modeHeight); + + GUI.DrawTexture(myRect1, TextureResources.LightModeTexture2D); + + hoveredSegment = GetHoveredSegment(myRect1, segmentId, hoveredSegment, segmentLight); + + // COUNTER + hoveredSegment = RenderCounter(segmentId, offsetScreenPos, modeWidth, modeHeight, zoom, segmentLights, hoveredSegment); + + if (vehicleType != ExtVehicleType.None) { + // Info sign + var infoWidth = 56.125f * zoom; + var infoHeight = 51.375f * zoom; + + int numInfos = 0; + for (int k = 0; k < TrafficManagerTool.InfoSignsToDisplay.Length; ++k) { + if ((TrafficManagerTool.InfoSignsToDisplay[k] & vehicleType) == ExtVehicleType.None) + continue; + var infoRect = new Rect(offsetScreenPos.x + modeWidth / 2f + 7f * zoom * (float)(numInfos + 1) + infoWidth * (float)numInfos, offsetScreenPos.y - infoHeight / 2f, infoWidth, infoHeight); + guiColor.a = MainTool.GetHandleAlpha(false); + GUI.DrawTexture(infoRect, TextureResources.VehicleInfoSignTextures[TrafficManagerTool.InfoSignsToDisplay[k]]); + ++numInfos; + } + } + + ExtSegment seg = segMan.ExtSegments[segmentId]; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; + if (seg.oneWay && segEnd.outgoing) continue; + + bool hasLeftSegment; + bool hasForwardSegment; + bool hasRightSegment; + segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[segmentId], out hasLeftSegment, out hasForwardSegment, out hasRightSegment); + + switch (segmentLight.CurrentMode) { + case LightMode.Simple: + hoveredSegment = SimpleManualSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); + break; + case LightMode.SingleLeft: + hoveredSegment = LeftForwardRManualSegmentLightMode(hasLeftSegment, segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment, hasForwardSegment, hasRightSegment); + break; + case LightMode.SingleRight: + hoveredSegment = RightForwardLSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, hasForwardSegment, hasLeftSegment, segmentLight, hasRightSegment, hoveredSegment); + break; + default: + // left arrow light + if (hasLeftSegment) + hoveredSegment = LeftArrowLightMode(segmentId, lightWidth, hasRightSegment, hasForwardSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); + + // forward arrow light + if (hasForwardSegment) + hoveredSegment = ForwardArrowLightMode(segmentId, lightWidth, hasRightSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); + + // right arrow light + if (hasRightSegment) + hoveredSegment = RightArrowLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); + break; + } + } + } + } + + if (hoveredSegment) return; + _hoveredButton[0] = 0; + _hoveredButton[1] = 0; + } + + public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { + if (SelectedNodeId != 0) { + RenderManualNodeOverlays(cameraInfo); + } else { + RenderManualSelectionOverlay(cameraInfo); + } + } + + private bool RenderManualPedestrianLightSwitch(float zoom, int segmentId, Vector3 screenPos, float lightWidth, + ICustomSegmentLights segmentLights, bool hoveredSegment) { + if (segmentLights.PedestrianLightState == null) + return false; + + var guiColor = GUI.color; + var manualPedestrianWidth = 36f * zoom; + var manualPedestrianHeight = 35f * zoom; + + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && (_hoveredButton[1] == 1 || _hoveredButton[1] == 2)); + + GUI.color = guiColor; + + var myRect2 = new Rect(screenPos.x - manualPedestrianWidth / 2 - lightWidth + 5f * zoom, + screenPos.y - manualPedestrianHeight / 2 - 9f * zoom, manualPedestrianWidth, manualPedestrianHeight); + + GUI.DrawTexture(myRect2, segmentLights.ManualPedestrianMode ? TextureResources.PedestrianModeManualTexture2D : TextureResources.PedestrianModeAutomaticTexture2D); + + if (!myRect2.Contains(Event.current.mousePosition)) + return hoveredSegment; + + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 1; + + if (!MainTool.CheckClicked()) + return true; + + segmentLights.ManualPedestrianMode = !segmentLights.ManualPedestrianMode; + return true; + } + + private bool IsPedestrianLightHovered(Rect myRect3, int segmentId, bool hoveredSegment, ICustomSegmentLights segmentLights) { + if (!myRect3.Contains(Event.current.mousePosition)) + return hoveredSegment; + if (segmentLights.PedestrianLightState == null) + return false; + + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 2; + + if (!MainTool.CheckClicked()) + return true; + + if (!segmentLights.ManualPedestrianMode) { + segmentLights.ManualPedestrianMode = true; + } else { + segmentLights.ChangeLightPedestrian(); + } + return true; + } + + private bool GetHoveredSegment(Rect myRect1, int segmentId, bool hoveredSegment, ICustomSegmentLight segmentDict) { + if (!myRect1.Contains(Event.current.mousePosition)) + return hoveredSegment; + + //Log.Message("mouse in myRect1"); + _hoveredButton[0] = segmentId; + _hoveredButton[1] = -1; + + if (!MainTool.CheckClicked()) + return true; + segmentDict.ToggleMode(); + return true; + } + + private bool RenderCounter(int segmentId, Vector3 screenPos, float modeWidth, float modeHeight, float zoom, + ICustomSegmentLights segmentLights, bool hoveredSegment) { + SetAlpha(segmentId, 0); + + var myRectCounter = new Rect(screenPos.x - modeWidth / 2, screenPos.y - modeHeight / 2 - 6f * zoom, modeWidth, modeHeight); + + GUI.DrawTexture(myRectCounter, TextureResources.LightCounterTexture2D); + + var counterSize = 20f * zoom; + + var counter = segmentLights.LastChange(); + + var myRectCounterNum = new Rect(screenPos.x - counterSize + 15f * zoom + (counter >= 10 ? -5 * zoom : 0f), + screenPos.y - counterSize + 11f * zoom, counterSize, counterSize); + + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (!myRectCounter.Contains(Event.current.mousePosition)) + return hoveredSegment; + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 0; + return true; + } + + private bool SimpleManualSegmentLightMode(int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, + float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { + SetAlpha(segmentId, 3); + + var myRect4 = + new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + switch (segmentDict.LightMain) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect4, TextureResources.GreenLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect4, TextureResources.RedLightTexture2D); + break; + } + + if (!myRect4.Contains(Event.current.mousePosition)) + return hoveredSegment; + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 3; + + if (!MainTool.CheckClicked()) + return true; + segmentDict.ChangeMainLight(); + return true; + } + + private bool LeftForwardRManualSegmentLightMode(bool hasLeftSegment, int segmentId, Vector3 screenPos, float lightWidth, + float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment, + bool hasForwardSegment, bool hasRightSegment) { + if (hasLeftSegment) { + // left arrow light + SetAlpha(segmentId, 3); + + var myRect4 = + new Rect(screenPos.x - lightWidth / 2 - lightWidth * 2 - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + switch (segmentDict.LightLeft) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); + break; + } + + if (myRect4.Contains(Event.current.mousePosition)) { + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 3; + hoveredSegment = true; + + if (MainTool.CheckClicked()) { + segmentDict.ChangeLeftLight(); + } + } + } + + // forward-right arrow light + SetAlpha(segmentId, 4); + + var myRect5 = + new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + if (hasForwardSegment && hasRightSegment) { + switch (segmentDict.LightMain) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect5, TextureResources.GreenLightForwardRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect5, TextureResources.RedLightForwardRightTexture2D); + break; + } + } else if (!hasRightSegment) { + switch (segmentDict.LightMain) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect5, TextureResources.GreenLightStraightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect5, TextureResources.RedLightStraightTexture2D); + break; + } + } else { + switch (segmentDict.LightMain) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); + break; + } + } + + if (!myRect5.Contains(Event.current.mousePosition)) + return hoveredSegment; + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 4; + + if (!MainTool.CheckClicked()) + return true; + segmentDict.ChangeMainLight(); + return true; + } + + private bool RightForwardLSegmentLightMode(int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, + float zoom, float lightHeight, bool hasForwardSegment, bool hasLeftSegment, ICustomSegmentLight segmentDict, + bool hasRightSegment, bool hoveredSegment) { + SetAlpha(segmentId, 3); + + var myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth * 2 - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + if (hasForwardSegment && hasLeftSegment) { + switch (segmentDict.LightLeft) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect4, TextureResources.GreenLightForwardLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect4, TextureResources.RedLightForwardLeftTexture2D); + break; + } + } else if (!hasLeftSegment) { + if (!hasRightSegment) { + myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + } + + switch (segmentDict.LightMain) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect4, TextureResources.GreenLightStraightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect4, TextureResources.RedLightStraightTexture2D); + break; + } + } else { + if (!hasRightSegment) { + myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + } + + switch (segmentDict.LightMain) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); + break; + } + } + + + if (myRect4.Contains(Event.current.mousePosition)) { + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 3; + hoveredSegment = true; + + if (MainTool.CheckClicked()) { + segmentDict.ChangeMainLight(); + } + } + + var guiColor = GUI.color; + // right arrow light + if (hasRightSegment) + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == 4); + + GUI.color = guiColor; + + var myRect5 = + new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + switch (segmentDict.LightRight) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); + break; + } + + + if (!myRect5.Contains(Event.current.mousePosition)) + return hoveredSegment; + + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 4; + + if (!MainTool.CheckClicked()) + return true; + segmentDict.ChangeRightLight(); + return true; + } + + private bool LeftArrowLightMode(int segmentId, float lightWidth, bool hasRightSegment, + bool hasForwardSegment, Vector3 screenPos, float pedestrianWidth, float zoom, float lightHeight, + ICustomSegmentLight segmentDict, bool hoveredSegment) { + SetAlpha(segmentId, 3); + + var offsetLight = lightWidth; + + if (hasRightSegment) + offsetLight += lightWidth; + + if (hasForwardSegment) + offsetLight += lightWidth; + + var myRect4 = + new Rect(screenPos.x - lightWidth / 2 - offsetLight - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + switch (segmentDict.LightLeft) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); + break; + } + + if (!myRect4.Contains(Event.current.mousePosition)) + return hoveredSegment; + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 3; + + if (!MainTool.CheckClicked()) + return true; + segmentDict.ChangeLeftLight(); + + if (!hasForwardSegment) { + segmentDict.ChangeMainLight(); + } + return true; + } + + private bool ForwardArrowLightMode(int segmentId, float lightWidth, bool hasRightSegment, + Vector3 screenPos, float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, + bool hoveredSegment) { + SetAlpha(segmentId, 4); + + var offsetLight = lightWidth; + + if (hasRightSegment) + offsetLight += lightWidth; + + var myRect6 = + new Rect(screenPos.x - lightWidth / 2 - offsetLight - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + switch (segmentDict.LightMain) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect6, TextureResources.GreenLightStraightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect6, TextureResources.RedLightStraightTexture2D); + break; + } + + if (!myRect6.Contains(Event.current.mousePosition)) + return hoveredSegment; + + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 4; + + if (!MainTool.CheckClicked()) + return true; + segmentDict.ChangeMainLight(); + return true; + } + + private bool RightArrowLightMode(int segmentId, Vector3 screenPos, float lightWidth, + float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { + SetAlpha(segmentId, 5); + + var myRect5 = + new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, + screenPos.y - lightHeight / 2, lightWidth, lightHeight); + + switch (segmentDict.LightRight) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); + break; + } + + if (!myRect5.Contains(Event.current.mousePosition)) + return hoveredSegment; + + _hoveredButton[0] = segmentId; + _hoveredButton[1] = 5; + + if (!MainTool.CheckClicked()) + return true; + segmentDict.ChangeRightLight(); + return true; + } + + private Vector3 CalculateNodePositionForSegment(NetNode node, int segmentId) { + var position = node.m_position; + + var segment = Singleton.instance.m_segments.m_buffer[segmentId]; + if (segment.m_startNode == SelectedNodeId) { + position.x += segment.m_startDirection.x * 10f; + position.y += segment.m_startDirection.y * 10f; + position.z += segment.m_startDirection.z * 10f; + } else { + position.x += segment.m_endDirection.x * 10f; + position.y += segment.m_endDirection.y * 10f; + position.z += segment.m_endDirection.z * 10f; + } + return position; + } + + private Vector3 CalculateNodePositionForSegment(NetNode node, ref NetSegment segment) { + var position = node.m_position; + + const float offset = 25f; + + if (segment.m_startNode == SelectedNodeId) { + position.x += segment.m_startDirection.x * offset; + position.y += segment.m_startDirection.y * offset; + position.z += segment.m_startDirection.z * offset; + } else { + position.x += segment.m_endDirection.x * offset; + position.y += segment.m_endDirection.y * offset; + position.z += segment.m_endDirection.z * offset; + } + + return position; + } + + private void SetAlpha(int segmentId, int buttonId) { + var guiColor = GUI.color; + + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == buttonId); + + GUI.color = guiColor; + } + + private void RenderManualSelectionOverlay(RenderManager.CameraInfo cameraInfo) { + if (HoveredNodeId == 0) return; + + MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, false, false); + /*var segment = Singleton.instance.m_segments.m_buffer[Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_segment0]; + + //if ((node.m_flags & NetNode.Flags.TrafficLights) == NetNode.Flags.None) return; + Bezier3 bezier; + bezier.a = Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_position; + bezier.d = Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_position; + + var color = MainTool.GetToolColor(false, false); + + NetSegment.CalculateMiddlePoints(bezier.a, segment.m_startDirection, bezier.d, + segment.m_endDirection, false, false, out bezier.b, out bezier.c); + MainTool.DrawOverlayBezier(cameraInfo, bezier, color);*/ + } + + private void RenderManualNodeOverlays(RenderManager.CameraInfo cameraInfo) { + if (!TrafficLightSimulationManager.Instance.HasManualSimulation(SelectedNodeId)) { + return; + } + + MainTool.DrawNodeCircle(cameraInfo, SelectedNodeId, true, false); + } + + public override void Cleanup() { + if (SelectedNodeId == 0) return; + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + if (!tlsMan.HasManualSimulation(SelectedNodeId)) { + return; + } + + tlsMan.RemoveNodeFromSimulation(SelectedNodeId, true, false); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/PrioritySignsTool.cs b/TLM/TLM/UI/SubTools/PrioritySignsTool.cs index a7d0ee0b6..0fb1d4fd2 100644 --- a/TLM/TLM/UI/SubTools/PrioritySignsTool.cs +++ b/TLM/TLM/UI/SubTools/PrioritySignsTool.cs @@ -22,6 +22,8 @@ using TrafficManager.Traffic.Data; namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + public class PrioritySignsTool : SubTool { public enum PrioritySignsMassEditMode { MainYield = 0, diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 7d6d167b4..e008f5a71 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -1,659 +1,653 @@ -using ColossalFramework; -using ColossalFramework.Math; -using ColossalFramework.UI; -using CSUtil.Commons; -using GenericGameBridge.Service; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Custom.AI; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.TrafficLight; -using TrafficManager.Util; -using UnityEngine; -using static ColossalFramework.UI.UITextureAtlas; -using static TrafficManager.Util.SegmentLaneTraverser; - -namespace TrafficManager.UI.SubTools { - public class SpeedLimitsTool : SubTool { - /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH - private const int GuiSpeedSignSize = 80; - private readonly float speedLimitSignSize = 70f; - - private bool _cursorInSecondaryPanel; - - /// Currently selected speed limit on the limits palette - private float currentPaletteSpeedLimit = -1f; - - private bool overlayHandleHovered; - private Dictionary> segmentCenterByDir = new Dictionary>(); - - private Rect paletteWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * (GuiSpeedSignSize + 5), 150)); - private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); - private HashSet currentlyVisibleSegmentIds; - private bool defaultsWindowVisible = false; - private int currentInfoIndex = -1; - private float currentSpeedLimit = -1f; - private Texture2D RoadTexture { - get { - if (roadTexture == null) { - roadTexture = new Texture2D(GuiSpeedSignSize, GuiSpeedSignSize); - } - return roadTexture; - } - } - private Texture2D roadTexture = null; - private bool showLimitsPerLane = false; - - public SpeedLimitsTool(TrafficManagerTool mainTool) : base(mainTool) { - currentlyVisibleSegmentIds = new HashSet(); - } - - public override bool IsCursorInPanel() { - return base.IsCursorInPanel() || _cursorInSecondaryPanel; - } - - public override void OnActivate() { - - } - - public override void OnPrimaryClickOverlay() { - - } - - public override void OnToolGUI(Event e) { - base.OnToolGUI(e); - - var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph - ? Translation.GetString("Miles_per_hour") - : Translation.GetString("Kilometers_per_hour")) + ")"; - paletteWindowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph - ? 10 * (GuiSpeedSignSize + 5) - : 8 * (GuiSpeedSignSize + 5); - paletteWindowRect = GUILayout.Window(254, paletteWindowRect, _guiSpeedLimitsWindow, - Translation.GetString("Speed_limits") + unitTitle, - WindowStyle); - if (defaultsWindowVisible) { - defaultsWindowRect = GUILayout.Window( - 258, defaultsWindowRect, _guiDefaultsWindow, - Translation.GetString("Default_speed_limits"), - WindowStyle); - } - _cursorInSecondaryPanel = paletteWindowRect.Contains(Event.current.mousePosition) - || (defaultsWindowVisible - && defaultsWindowRect.Contains(Event.current.mousePosition)); - - //overlayHandleHovered = false; - //ShowSigns(false); - } - - public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { - - } - - public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { - if (viewOnly && !Options.speedLimitsOverlay) - return; - - overlayHandleHovered = false; - ShowSigns(viewOnly); - } - - public override void Cleanup() { - segmentCenterByDir.Clear(); - currentlyVisibleSegmentIds.Clear(); - lastCamPos = null; - lastCamRot = null; - currentInfoIndex = -1; - currentSpeedLimit = -1f; - } - - private Quaternion? lastCamRot = null; - private Vector3? lastCamPos = null; - - private void ShowSigns(bool viewOnly) { - Quaternion camRot = Camera.main.transform.rotation; - Vector3 camPos = Camera.main.transform.position; - - NetManager netManager = Singleton.instance; - SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance; - - if (lastCamPos == null || lastCamRot == null || !lastCamRot.Equals(camRot) || !lastCamPos.Equals(camPos)) { - // cache visible segments - currentlyVisibleSegmentIds.Clear(); - - for (uint segmentId = 1; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { - if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) { - continue; - } - /*if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) - continue;*/ - - if ((netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos).magnitude > TrafficManagerTool.MaxOverlayDistance) - continue; // do not draw if too distant - - Vector3 screenPos; - bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); - - if (! visible) - continue; - - if (!speedLimitManager.MayHaveCustomSpeedLimits((ushort)segmentId, ref netManager.m_segments.m_buffer[segmentId])) - continue; - - currentlyVisibleSegmentIds.Add((ushort)segmentId); - } - - lastCamPos = camPos; - lastCamRot = camRot; - } - - bool handleHovered = false; - foreach (ushort segmentId in currentlyVisibleSegmentIds) { - Vector3 screenPos; - bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); - - if (!visible) - continue; - - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - - // draw speed limits - if (MainTool.GetToolMode() != ToolMode.VehicleRestrictions || segmentId != SelectedSegmentId) { // no speed limit overlay on selected segment when in vehicle restrictions mode - if (drawSpeedLimitHandles((ushort)segmentId, ref netManager.m_segments.m_buffer[segmentId], viewOnly, ref camPos)) - handleHovered = true; - } - } - overlayHandleHovered = handleHovered; - } - - /// - /// The window for setting the defaullt speeds per road type - /// - /// - private void _guiDefaultsWindow(int num) { - var mainNetInfos = SpeedLimitManager.Instance.GetCustomizableNetInfos(); - - if (mainNetInfos == null || mainNetInfos.Count <= 0) { - Log._Debug($"mainNetInfos={mainNetInfos?.Count}"); - DragWindow(ref defaultsWindowRect); - return; - } - - bool updateRoadTex = false; - if (currentInfoIndex < 0 || currentInfoIndex >= mainNetInfos.Count) { - currentInfoIndex = 0; - updateRoadTex = true; - Log._Debug($"set currentInfoIndex to 0"); - } - - NetInfo info = mainNetInfos[currentInfoIndex]; - if (updateRoadTex) - UpdateRoadTex(info); - - if (currentSpeedLimit < 0f) { - currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); - Log._Debug($"set currentSpeedLimit to {currentSpeedLimit}"); - } - //Log._Debug($"currentInfoIndex={currentInfoIndex} currentSpeedLimitIndex={currentSpeedLimitIndex}"); - - // Road type label - GUILayout.BeginVertical(); - GUILayout.Space(10); - GUILayout.Label(Translation.GetString("Road_type") + ":"); - GUILayout.EndVertical(); - - // switch between NetInfos - GUILayout.BeginHorizontal(); - - GUILayout.BeginVertical(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("←", GUILayout.Width(50))) { - currentInfoIndex = - (currentInfoIndex + mainNetInfos.Count - 1) % mainNetInfos.Count; - info = mainNetInfos[currentInfoIndex]; - currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); - UpdateRoadTex(info); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndVertical(); - - GUILayout.FlexibleSpace(); - GUILayout.BeginVertical(); - GUILayout.FlexibleSpace(); - - // NetInfo thumbnail - GUILayout.Box(RoadTexture, GUILayout.Height(GuiSpeedSignSize)); - GUILayout.FlexibleSpace(); - - GUILayout.EndVertical(); - GUILayout.FlexibleSpace(); - - GUILayout.BeginVertical(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("→", GUILayout.Width(50))) { - currentInfoIndex = (currentInfoIndex + 1) % mainNetInfos.Count; - info = mainNetInfos[currentInfoIndex]; - currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); - UpdateRoadTex(info); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - - var centeredTextStyle = new GUIStyle("label") { alignment = TextAnchor.MiddleCenter }; - - // NetInfo name - GUILayout.Label(info.name, centeredTextStyle); - - // Default speed limit label - GUILayout.BeginVertical(); - GUILayout.Space(10); - GUILayout.Label(Translation.GetString("Default_speed_limit") + ":"); - GUILayout.EndVertical(); - - // switch between speed limits - GUILayout.BeginHorizontal(); - - GUILayout.BeginVertical(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("←", GUILayout.Width(50))) { - // currentSpeedLimit = (currentSpeedLimitIndex + SpeedLimitManager.Instance.AvailableSpeedLimits.Count - 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; - currentSpeedLimit = SpeedLimit.GetPrevious(currentSpeedLimit); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndVertical(); - - GUILayout.FlexibleSpace(); - - GUILayout.BeginVertical(); - GUILayout.FlexibleSpace(); - - // speed limit sign - GUILayout.Box(TextureResources.GetSpeedLimitTexture(currentSpeedLimit), - GUILayout.Width(GuiSpeedSignSize), - GUILayout.Height(GuiSpeedSignSize)); - GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph - ? Translation.GetString("Miles_per_hour") - : Translation.GetString("Kilometers_per_hour")); - - GUILayout.FlexibleSpace(); - GUILayout.EndVertical(); - - GUILayout.FlexibleSpace(); - - GUILayout.BeginVertical(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("→", GUILayout.Width(50))) { - // currentSpeedLimitIndex = (currentSpeedLimitIndex + 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; - currentSpeedLimit = SpeedLimit.GetNext(currentSpeedLimit); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - - // Save & Apply - GUILayout.BeginVertical(); - GUILayout.Space(10); - - GUILayout.BeginHorizontal(); - - // Close button. TODO: Make more visible or obey 'Esc' pressed or something - GUILayout.FlexibleSpace(); - if (GUILayout.Button("X", GUILayout.Width(80))) { - defaultsWindowVisible = false; - } - - GUILayout.FlexibleSpace(); - if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { - SpeedLimitManager.Instance.FixCurrentSpeedLimits(info); - SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); - } - - GUILayout.FlexibleSpace(); - if (GUILayout.Button( - Translation.GetString("Save") + " & " + Translation.GetString("Apply"), - GUILayout.Width(160))) { - SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); - SpeedLimitManager.Instance.ClearCurrentSpeedLimits(info); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.EndVertical(); - - DragWindow(ref defaultsWindowRect); - } - - private void UpdateRoadTex(NetInfo info) { - if (info != null) { - if (info.m_Atlas != null && info.m_Atlas.material != null && info.m_Atlas.material.mainTexture != null && info.m_Atlas.material.mainTexture is Texture2D) { - Texture2D mainTex = (Texture2D)info.m_Atlas.material.mainTexture; - SpriteInfo spriteInfo = info.m_Atlas[info.m_Thumbnail]; - - if (spriteInfo != null && spriteInfo.texture != null && spriteInfo.texture.width > 0 && spriteInfo.texture.height > 0) { - try { - roadTexture = new Texture2D((int)spriteInfo.texture.width, (int)spriteInfo.texture.height, TextureFormat.ARGB32, false); - roadTexture.SetPixels(0, 0, roadTexture.width, roadTexture.height, mainTex.GetPixels((int)(spriteInfo.region.x * mainTex.width), (int)(spriteInfo.region.y * mainTex.height), (int)(spriteInfo.region.width * mainTex.width), (int)(spriteInfo.region.height * mainTex.height))); - roadTexture.Apply(); - return; - } catch (Exception e) { - Log.Warning($"Could not get texture from NetInfo {info.name}: {e.ToString()}"); - } - } - } - } - - // fallback to "noimage" texture - roadTexture = TextureResources.NoImageTexture2D; - } - - /// - /// The window for selecting and applying a speed limit - /// - /// - private void _guiSpeedLimitsWindow(int num) { - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - - var oldColor = GUI.color; - var allSpeedLimits = SpeedLimit.EnumerateSpeedLimits(SpeedUnit.CurrentlyConfigured); - allSpeedLimits.Add(0); // add last item: no limit - - var showMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; - var column = 0u; // break palette to a new line at breakColumn - var breakColumn = showMph ? SpeedLimit.BREAK_PALETTE_COLUMN_MPH - : SpeedLimit.BREAK_PALETTE_COLUMN_KMPH; - - foreach (var speedLimit in allSpeedLimits) { - // Highlight palette item if it is very close to its float speed - if (SpeedLimit.NearlyEqual(currentPaletteSpeedLimit, speedLimit)) { - GUI.color = Color.gray; - } - - _guiSpeedLimitsWindow_AddButton(showMph, speedLimit); - GUI.color = oldColor; - - // TODO: This can be calculated from SpeedLimit MPH or KMPH limit constants - column++; - if (column % breakColumn == 0) { - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - } - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - //--------------------- - // UI buttons row - //--------------------- - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button(Translation.GetString("Default_speed_limits"), - GUILayout.Width(200))) { - TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Defaults"); - defaultsWindowVisible = true; - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - //--------------------- - // Checkboxes row - //--------------------- - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - showLimitsPerLane = GUILayout.Toggle(showLimitsPerLane, Translation.GetString("Show_lane-wise_speed_limits")); - GUILayout.FlexibleSpace(); - - // Display MPH checkbox, if ticked will save global config - var displayMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; - displayMph = GUILayout.Toggle(displayMph, Translation.GetString("Display_speed_limits_mph")); - if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph != displayMph) { - Options.setDisplayInMPH(displayMph); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - DragWindow(ref paletteWindowRect); - } - - /// Helper to create speed limit sign + label below converted to the opposite unit - /// Config value from GlobalConfig.I.M.ShowMPH - /// The float speed to show - private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { - // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added - GUILayout.BeginVertical(); - - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - var signSize = TrafficManagerTool.AdaptWidth(GuiSpeedSignSize); - if (GUILayout.Button( - TextureResources.GetSpeedLimitTexture(speedLimit), - GUILayout.Width(signSize), - GUILayout.Height(signSize * SpeedLimit.GetVerticalTextureScale()))) { - currentPaletteSpeedLimit = speedLimit; - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - // For MPH setting display KM/H below, for KM/H setting display MPH - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - GUILayout.Label(showMph ? SpeedLimit.ToKmphPreciseString(speedLimit) - : SpeedLimit.ToMphPreciseString(speedLimit)); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.EndVertical(); - } - - private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { - if (viewOnly && !Options.speedLimitsOverlay) { - return false; - } - - var center = segment.m_bounds.center; - var netManager = Singleton.instance; - - var hovered = false; - var speedLimitToSet = viewOnly ? -1f : currentPaletteSpeedLimit; - - bool showPerLane = showLimitsPerLane; - if (!viewOnly) { - showPerLane = showLimitsPerLane ^ (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)); - } - - // US signs are rectangular, all other are round - var speedLimitSignVerticalScale = SpeedLimit.GetVerticalTextureScale(); - - if (showPerLane) { - // show individual speed limit handle per lane - int numDirections; - var numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, SpeedLimitManager.VEHICLE_TYPES); - - var segmentInfo = segment.Info; - var yu = (segment.m_endDirection - segment.m_startDirection).normalized; - var xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; - /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) { - xu = -xu; - }*/ - var f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates - var zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu; - - uint x = 0; - var guiColor = GUI.color; - var sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes( - segmentId, ref segment, null, SpeedLimitManager.LANE_TYPES, - SpeedLimitManager.VEHICLE_TYPES); - var onlyMonorailLanes = sortedLanes.Count > 0; - if (!viewOnly) { - foreach (LanePos laneData in sortedLanes) { - var laneIndex = laneData.laneIndex; - var laneInfo = segmentInfo.m_lanes[laneIndex]; - - if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None) { - onlyMonorailLanes = false; - break; - } - } - } - - var directions = new HashSet(); - var sortedLaneIndex = -1; - foreach (LanePos laneData in sortedLanes) { - ++sortedLaneIndex; - uint laneId = laneData.laneId; - byte laneIndex = laneData.laneIndex; - - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - if (!directions.Contains(laneInfo.m_finalDirection)) { - if (directions.Count > 0) - ++x; // space between different directions - directions.Add(laneInfo.m_finalDirection); - } - - var laneSpeedLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId); - var hoveredHandle = MainTool.DrawGenericOverlayGridTexture( - TextureResources.GetSpeedLimitTexture(laneSpeedLimit), - camPos, zero, f, f, xu, yu, x, 0, - speedLimitSignSize, speedLimitSignSize * speedLimitSignVerticalScale, - !viewOnly); - - if (!viewOnly - && !onlyMonorailLanes - && (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { - MainTool.DrawStaticSquareOverlayGridTexture( - TextureResources.VehicleInfoSignTextures[ExtVehicleType.PassengerTrain], - camPos, zero, f, xu, yu, x, 1, speedLimitSignSize); - } - if (hoveredHandle) { - hovered = true; - } - - if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { - SpeedLimitManager.Instance.SetSpeedLimit(segmentId, laneIndex, laneInfo, laneId, speedLimitToSet); - - if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { - SegmentLaneTraverser.Traverse(segmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { - if (data.segVisitData.initial) { - return true; - } - - if (sortedLaneIndex != data.sortedLaneIndex) { - return true; - } - - Constants.ServiceFactory.NetService.ProcessSegment(data.segVisitData.curSeg.segmentId, delegate (ushort curSegmentId, ref NetSegment curSegment) { - NetInfo.Lane curLaneInfo = curSegment.Info.m_lanes[data.curLanePos.laneIndex]; - SpeedLimitManager.Instance.SetSpeedLimit(curSegmentId, data.curLanePos.laneIndex, curLaneInfo, data.curLanePos.laneId, speedLimitToSet); - return true; - }); - - return true; - }); - } - } - - ++x; - } - } else { - // draw speedlimits over mean middle points of lane beziers - Dictionary segCenter; - if (!segmentCenterByDir.TryGetValue(segmentId, out segCenter)) { - segCenter = new Dictionary(); - segmentCenterByDir.Add(segmentId, segCenter); - TrafficManagerTool.CalculateSegmentCenterByDir(segmentId, segCenter); - } - - foreach (KeyValuePair e in segCenter) { - Vector3 screenPos; - var visible = MainTool.WorldToScreenPoint(e.Value, out screenPos); - - if (!visible) { - continue; - } - - var zoom = 1.0f / (e.Value - camPos).magnitude * 100f * MainTool.GetBaseZoom(); - var size = (viewOnly ? 0.8f : 1f) * speedLimitSignSize * zoom; - var guiColor = GUI.color; - var boundingBox = new Rect(screenPos.x - (size / 2), - screenPos.y - (size / 2), - size, - size * speedLimitSignVerticalScale); - var hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); - - guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); - if (hoveredHandle) { - // mouse hovering over sign - hovered = true; - } - - // Draw something right here, the road sign texture - GUI.color = guiColor; - var displayLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key); - var tex = TextureResources.GetSpeedLimitTexture(displayLimit); - GUI.DrawTexture(boundingBox, tex); - - if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { - // change the speed limit to the selected one - //Log._Debug($"Setting speed limit of segment {segmentId}, dir {e.Key.ToString()} to {speedLimitToSet}"); - SpeedLimitManager.Instance.SetSpeedLimit(segmentId, e.Key, currentPaletteSpeedLimit); - - if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { - - NetInfo.Direction normDir = e.Key; - if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { - normDir = NetInfo.InvertDirection(normDir); - } - - SegmentLaneTraverser.Traverse(segmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { - if (data.segVisitData.initial) { - return true; - } - bool reverse = data.segVisitData.viaStartNode == data.segVisitData.viaInitialStartNode; - - ushort otherSegmentId = data.segVisitData.curSeg.segmentId; - NetInfo otherSegmentInfo = netManager.m_segments.m_buffer[otherSegmentId].Info; - uint laneId = data.curLanePos.laneId; - byte laneIndex = data.curLanePos.laneIndex; - NetInfo.Lane laneInfo = otherSegmentInfo.m_lanes[laneIndex]; - - NetInfo.Direction otherNormDir = laneInfo.m_finalDirection; - if ((netManager.m_segments.m_buffer[otherSegmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None ^ - reverse) { - otherNormDir = NetInfo.InvertDirection(otherNormDir); - } - - if (otherNormDir == normDir) { - SpeedLimitManager.Instance.SetSpeedLimit(otherSegmentId, laneInfo.m_finalDirection, speedLimitToSet); - } - - return true; - }); - } - } - - guiColor.a = 1f; - GUI.color = guiColor; - } - } - return hovered; - } - } -} +namespace TrafficManager.UI.SubTools { + using System; + using System.Collections.Generic; + using ColossalFramework; + using CSUtil.Commons; + using GenericGameBridge.Service; + using Manager.Impl; + using State; + using Traffic; + using Traffic.Data; + using UnityEngine; + using Util; + using static ColossalFramework.UI.UITextureAtlas; + using static Util.SegmentLaneTraverser; + + public class SpeedLimitsTool : SubTool { + /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH + private const int GuiSpeedSignSize = 80; + private readonly float speedLimitSignSize = 70f; + + private bool _cursorInSecondaryPanel; + + /// Currently selected speed limit on the limits palette + private float currentPaletteSpeedLimit = -1f; + + private bool overlayHandleHovered; + private Dictionary> segmentCenterByDir = new Dictionary>(); + + private Rect paletteWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * (GuiSpeedSignSize + 5), 150)); + private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); + private HashSet currentlyVisibleSegmentIds; + private bool defaultsWindowVisible = false; + private int currentInfoIndex = -1; + private float currentSpeedLimit = -1f; + private Texture2D RoadTexture { + get { + if (roadTexture == null) { + roadTexture = new Texture2D(GuiSpeedSignSize, GuiSpeedSignSize); + } + return roadTexture; + } + } + private Texture2D roadTexture = null; + private bool showLimitsPerLane = false; + + public SpeedLimitsTool(TrafficManagerTool mainTool) : base(mainTool) { + currentlyVisibleSegmentIds = new HashSet(); + } + + public override bool IsCursorInPanel() { + return base.IsCursorInPanel() || _cursorInSecondaryPanel; + } + + public override void OnActivate() { + + } + + public override void OnPrimaryClickOverlay() { + + } + + public override void OnToolGUI(Event e) { + base.OnToolGUI(e); + + var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? Translation.GetString("Miles_per_hour") + : Translation.GetString("Kilometers_per_hour")) + ")"; + paletteWindowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? 10 * (GuiSpeedSignSize + 5) + : 8 * (GuiSpeedSignSize + 5); + paletteWindowRect = GUILayout.Window(254, paletteWindowRect, _guiSpeedLimitsWindow, + Translation.GetString("Speed_limits") + unitTitle, + WindowStyle); + if (defaultsWindowVisible) { + defaultsWindowRect = GUILayout.Window( + 258, defaultsWindowRect, _guiDefaultsWindow, + Translation.GetString("Default_speed_limits"), + WindowStyle); + } + _cursorInSecondaryPanel = paletteWindowRect.Contains(Event.current.mousePosition) + || (defaultsWindowVisible + && defaultsWindowRect.Contains(Event.current.mousePosition)); + + //overlayHandleHovered = false; + //ShowSigns(false); + } + + public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { + + } + + public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { + if (viewOnly && !Options.speedLimitsOverlay) + return; + + overlayHandleHovered = false; + ShowSigns(viewOnly); + } + + public override void Cleanup() { + segmentCenterByDir.Clear(); + currentlyVisibleSegmentIds.Clear(); + lastCamPos = null; + lastCamRot = null; + currentInfoIndex = -1; + currentSpeedLimit = -1f; + } + + private Quaternion? lastCamRot = null; + private Vector3? lastCamPos = null; + + private void ShowSigns(bool viewOnly) { + Quaternion camRot = Camera.main.transform.rotation; + Vector3 camPos = Camera.main.transform.position; + + NetManager netManager = Singleton.instance; + SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance; + + if (lastCamPos == null || lastCamRot == null || !lastCamRot.Equals(camRot) || !lastCamPos.Equals(camPos)) { + // cache visible segments + currentlyVisibleSegmentIds.Clear(); + + for (uint segmentId = 1; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { + if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) { + continue; + } + /*if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) + continue;*/ + + if ((netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos).magnitude > TrafficManagerTool.MaxOverlayDistance) + continue; // do not draw if too distant + + Vector3 screenPos; + bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); + + if (! visible) + continue; + + if (!speedLimitManager.MayHaveCustomSpeedLimits((ushort)segmentId, ref netManager.m_segments.m_buffer[segmentId])) + continue; + + currentlyVisibleSegmentIds.Add((ushort)segmentId); + } + + lastCamPos = camPos; + lastCamRot = camRot; + } + + bool handleHovered = false; + foreach (ushort segmentId in currentlyVisibleSegmentIds) { + Vector3 screenPos; + bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); + + if (!visible) + continue; + + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + + // draw speed limits + if (MainTool.GetToolMode() != ToolMode.VehicleRestrictions || segmentId != SelectedSegmentId) { // no speed limit overlay on selected segment when in vehicle restrictions mode + if (drawSpeedLimitHandles((ushort)segmentId, ref netManager.m_segments.m_buffer[segmentId], viewOnly, ref camPos)) + handleHovered = true; + } + } + overlayHandleHovered = handleHovered; + } + + /// + /// The window for setting the defaullt speeds per road type + /// + /// + private void _guiDefaultsWindow(int num) { + var mainNetInfos = SpeedLimitManager.Instance.GetCustomizableNetInfos(); + + if (mainNetInfos == null || mainNetInfos.Count <= 0) { + Log._Debug($"mainNetInfos={mainNetInfos?.Count}"); + DragWindow(ref defaultsWindowRect); + return; + } + + bool updateRoadTex = false; + if (currentInfoIndex < 0 || currentInfoIndex >= mainNetInfos.Count) { + currentInfoIndex = 0; + updateRoadTex = true; + Log._Debug($"set currentInfoIndex to 0"); + } + + NetInfo info = mainNetInfos[currentInfoIndex]; + if (updateRoadTex) + UpdateRoadTex(info); + + if (currentSpeedLimit < 0f) { + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); + Log._Debug($"set currentSpeedLimit to {currentSpeedLimit}"); + } + //Log._Debug($"currentInfoIndex={currentInfoIndex} currentSpeedLimitIndex={currentSpeedLimitIndex}"); + + // Road type label + GUILayout.BeginVertical(); + GUILayout.Space(10); + GUILayout.Label(Translation.GetString("Road_type") + ":"); + GUILayout.EndVertical(); + + // switch between NetInfos + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("←", GUILayout.Width(50))) { + currentInfoIndex = + (currentInfoIndex + mainNetInfos.Count - 1) % mainNetInfos.Count; + info = mainNetInfos[currentInfoIndex]; + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); + UpdateRoadTex(info); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + + // NetInfo thumbnail + GUILayout.Box(RoadTexture, GUILayout.Height(GuiSpeedSignSize)); + GUILayout.FlexibleSpace(); + + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("→", GUILayout.Width(50))) { + currentInfoIndex = (currentInfoIndex + 1) % mainNetInfos.Count; + info = mainNetInfos[currentInfoIndex]; + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); + UpdateRoadTex(info); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + + var centeredTextStyle = new GUIStyle("label") { alignment = TextAnchor.MiddleCenter }; + + // NetInfo name + GUILayout.Label(info.name, centeredTextStyle); + + // Default speed limit label + GUILayout.BeginVertical(); + GUILayout.Space(10); + GUILayout.Label(Translation.GetString("Default_speed_limit") + ":"); + GUILayout.EndVertical(); + + // switch between speed limits + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("←", GUILayout.Width(50))) { + // currentSpeedLimit = (currentSpeedLimitIndex + SpeedLimitManager.Instance.AvailableSpeedLimits.Count - 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + currentSpeedLimit = SpeedLimit.GetPrevious(currentSpeedLimit); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + + // speed limit sign + GUILayout.Box(TextureResources.GetSpeedLimitTexture(currentSpeedLimit), + GUILayout.Width(GuiSpeedSignSize), + GUILayout.Height(GuiSpeedSignSize)); + GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? Translation.GetString("Miles_per_hour") + : Translation.GetString("Kilometers_per_hour")); + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("→", GUILayout.Width(50))) { + // currentSpeedLimitIndex = (currentSpeedLimitIndex + 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + currentSpeedLimit = SpeedLimit.GetNext(currentSpeedLimit); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + + // Save & Apply + GUILayout.BeginVertical(); + GUILayout.Space(10); + + GUILayout.BeginHorizontal(); + + // Close button. TODO: Make more visible or obey 'Esc' pressed or something + GUILayout.FlexibleSpace(); + if (GUILayout.Button("X", GUILayout.Width(80))) { + defaultsWindowVisible = false; + } + + GUILayout.FlexibleSpace(); + if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { + SpeedLimitManager.Instance.FixCurrentSpeedLimits(info); + SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); + } + + GUILayout.FlexibleSpace(); + if (GUILayout.Button( + Translation.GetString("Save") + " & " + Translation.GetString("Apply"), + GUILayout.Width(160))) { + SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); + SpeedLimitManager.Instance.ClearCurrentSpeedLimits(info); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.EndVertical(); + + DragWindow(ref defaultsWindowRect); + } + + private void UpdateRoadTex(NetInfo info) { + if (info != null) { + if (info.m_Atlas != null && info.m_Atlas.material != null && info.m_Atlas.material.mainTexture != null && info.m_Atlas.material.mainTexture is Texture2D) { + Texture2D mainTex = (Texture2D)info.m_Atlas.material.mainTexture; + SpriteInfo spriteInfo = info.m_Atlas[info.m_Thumbnail]; + + if (spriteInfo != null && spriteInfo.texture != null && spriteInfo.texture.width > 0 && spriteInfo.texture.height > 0) { + try { + roadTexture = new Texture2D((int)spriteInfo.texture.width, (int)spriteInfo.texture.height, TextureFormat.ARGB32, false); + roadTexture.SetPixels(0, 0, roadTexture.width, roadTexture.height, mainTex.GetPixels((int)(spriteInfo.region.x * mainTex.width), (int)(spriteInfo.region.y * mainTex.height), (int)(spriteInfo.region.width * mainTex.width), (int)(spriteInfo.region.height * mainTex.height))); + roadTexture.Apply(); + return; + } catch (Exception e) { + Log.Warning($"Could not get texture from NetInfo {info.name}: {e.ToString()}"); + } + } + } + } + + // fallback to "noimage" texture + roadTexture = TextureResources.NoImageTexture2D; + } + + /// + /// The window for selecting and applying a speed limit + /// + /// + private void _guiSpeedLimitsWindow(int num) { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + var oldColor = GUI.color; + var allSpeedLimits = SpeedLimit.EnumerateSpeedLimits(SpeedUnit.CurrentlyConfigured); + allSpeedLimits.Add(0); // add last item: no limit + + var showMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + var column = 0u; // break palette to a new line at breakColumn + var breakColumn = showMph ? SpeedLimit.BREAK_PALETTE_COLUMN_MPH + : SpeedLimit.BREAK_PALETTE_COLUMN_KMPH; + + foreach (var speedLimit in allSpeedLimits) { + // Highlight palette item if it is very close to its float speed + if (SpeedLimit.NearlyEqual(currentPaletteSpeedLimit, speedLimit)) { + GUI.color = Color.gray; + } + + _guiSpeedLimitsWindow_AddButton(showMph, speedLimit); + GUI.color = oldColor; + + // TODO: This can be calculated from SpeedLimit MPH or KMPH limit constants + column++; + if (column % breakColumn == 0) { + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + } + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + //--------------------- + // UI buttons row + //--------------------- + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button(Translation.GetString("Default_speed_limits"), + GUILayout.Width(200))) { + TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Defaults"); + defaultsWindowVisible = true; + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + //--------------------- + // Checkboxes row + //--------------------- + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + showLimitsPerLane = GUILayout.Toggle(showLimitsPerLane, Translation.GetString("Show_lane-wise_speed_limits")); + GUILayout.FlexibleSpace(); + + // Display MPH checkbox, if ticked will save global config + var displayMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + displayMph = GUILayout.Toggle(displayMph, Translation.GetString("Display_speed_limits_mph")); + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph != displayMph) { + Options.setDisplayInMPH(displayMph); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + DragWindow(ref paletteWindowRect); + } + + /// Helper to create speed limit sign + label below converted to the opposite unit + /// Config value from GlobalConfig.I.M.ShowMPH + /// The float speed to show + private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { + // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added + GUILayout.BeginVertical(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + var signSize = TrafficManagerTool.AdaptWidth(GuiSpeedSignSize); + if (GUILayout.Button( + TextureResources.GetSpeedLimitTexture(speedLimit), + GUILayout.Width(signSize), + GUILayout.Height(signSize * SpeedLimit.GetVerticalTextureScale()))) { + currentPaletteSpeedLimit = speedLimit; + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + // For MPH setting display KM/H below, for KM/H setting display MPH + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(showMph ? SpeedLimit.ToKmphPreciseString(speedLimit) + : SpeedLimit.ToMphPreciseString(speedLimit)); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.EndVertical(); + } + + private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { + if (viewOnly && !Options.speedLimitsOverlay) { + return false; + } + + var center = segment.m_bounds.center; + var netManager = Singleton.instance; + + var hovered = false; + var speedLimitToSet = viewOnly ? -1f : currentPaletteSpeedLimit; + + bool showPerLane = showLimitsPerLane; + if (!viewOnly) { + showPerLane = showLimitsPerLane ^ (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)); + } + + // US signs are rectangular, all other are round + var speedLimitSignVerticalScale = SpeedLimit.GetVerticalTextureScale(); + + if (showPerLane) { + // show individual speed limit handle per lane + int numDirections; + var numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, SpeedLimitManager.VEHICLE_TYPES); + + var segmentInfo = segment.Info; + var yu = (segment.m_endDirection - segment.m_startDirection).normalized; + var xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; + /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) { + xu = -xu; + }*/ + var f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates + var zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu; + + uint x = 0; + var guiColor = GUI.color; + var sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes( + segmentId, ref segment, null, SpeedLimitManager.LANE_TYPES, + SpeedLimitManager.VEHICLE_TYPES); + var onlyMonorailLanes = sortedLanes.Count > 0; + if (!viewOnly) { + foreach (LanePos laneData in sortedLanes) { + var laneIndex = laneData.laneIndex; + var laneInfo = segmentInfo.m_lanes[laneIndex]; + + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None) { + onlyMonorailLanes = false; + break; + } + } + } + + var directions = new HashSet(); + var sortedLaneIndex = -1; + foreach (LanePos laneData in sortedLanes) { + ++sortedLaneIndex; + uint laneId = laneData.laneId; + byte laneIndex = laneData.laneIndex; + + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + if (!directions.Contains(laneInfo.m_finalDirection)) { + if (directions.Count > 0) + ++x; // space between different directions + directions.Add(laneInfo.m_finalDirection); + } + + var laneSpeedLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId); + var hoveredHandle = MainTool.DrawGenericOverlayGridTexture( + TextureResources.GetSpeedLimitTexture(laneSpeedLimit), + camPos, zero, f, f, xu, yu, x, 0, + speedLimitSignSize, speedLimitSignSize * speedLimitSignVerticalScale, + !viewOnly); + + if (!viewOnly + && !onlyMonorailLanes + && (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { + MainTool.DrawStaticSquareOverlayGridTexture( + TextureResources.VehicleInfoSignTextures[ + LegacyExtVehicleType.ToNew(ExtVehicleType.PassengerTrain) + ], + camPos, zero, f, xu, yu, x, 1, speedLimitSignSize); + } + if (hoveredHandle) { + hovered = true; + } + + if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { + SpeedLimitManager.Instance.SetSpeedLimit(segmentId, laneIndex, laneInfo, laneId, speedLimitToSet); + + if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { + SegmentLaneTraverser.Traverse(segmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { + if (data.segVisitData.initial) { + return true; + } + + if (sortedLaneIndex != data.sortedLaneIndex) { + return true; + } + + Constants.ServiceFactory.NetService.ProcessSegment(data.segVisitData.curSeg.segmentId, delegate (ushort curSegmentId, ref NetSegment curSegment) { + NetInfo.Lane curLaneInfo = curSegment.Info.m_lanes[data.curLanePos.laneIndex]; + SpeedLimitManager.Instance.SetSpeedLimit(curSegmentId, data.curLanePos.laneIndex, curLaneInfo, data.curLanePos.laneId, speedLimitToSet); + return true; + }); + + return true; + }); + } + } + + ++x; + } + } else { + // draw speedlimits over mean middle points of lane beziers + Dictionary segCenter; + if (!segmentCenterByDir.TryGetValue(segmentId, out segCenter)) { + segCenter = new Dictionary(); + segmentCenterByDir.Add(segmentId, segCenter); + TrafficManagerTool.CalculateSegmentCenterByDir(segmentId, segCenter); + } + + foreach (KeyValuePair e in segCenter) { + Vector3 screenPos; + var visible = MainTool.WorldToScreenPoint(e.Value, out screenPos); + + if (!visible) { + continue; + } + + var zoom = 1.0f / (e.Value - camPos).magnitude * 100f * MainTool.GetBaseZoom(); + var size = (viewOnly ? 0.8f : 1f) * speedLimitSignSize * zoom; + var guiColor = GUI.color; + var boundingBox = new Rect(screenPos.x - (size / 2), + screenPos.y - (size / 2), + size, + size * speedLimitSignVerticalScale); + var hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); + + guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); + if (hoveredHandle) { + // mouse hovering over sign + hovered = true; + } + + // Draw something right here, the road sign texture + GUI.color = guiColor; + var displayLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key); + var tex = TextureResources.GetSpeedLimitTexture(displayLimit); + GUI.DrawTexture(boundingBox, tex); + + if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { + // change the speed limit to the selected one + //Log._Debug($"Setting speed limit of segment {segmentId}, dir {e.Key.ToString()} to {speedLimitToSet}"); + SpeedLimitManager.Instance.SetSpeedLimit(segmentId, e.Key, currentPaletteSpeedLimit); + + if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { + + NetInfo.Direction normDir = e.Key; + if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { + normDir = NetInfo.InvertDirection(normDir); + } + + SegmentLaneTraverser.Traverse(segmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { + if (data.segVisitData.initial) { + return true; + } + bool reverse = data.segVisitData.viaStartNode == data.segVisitData.viaInitialStartNode; + + ushort otherSegmentId = data.segVisitData.curSeg.segmentId; + NetInfo otherSegmentInfo = netManager.m_segments.m_buffer[otherSegmentId].Info; + uint laneId = data.curLanePos.laneId; + byte laneIndex = data.curLanePos.laneIndex; + NetInfo.Lane laneInfo = otherSegmentInfo.m_lanes[laneIndex]; + + NetInfo.Direction otherNormDir = laneInfo.m_finalDirection; + if ((netManager.m_segments.m_buffer[otherSegmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None ^ + reverse) { + otherNormDir = NetInfo.InvertDirection(otherNormDir); + } + + if (otherNormDir == normDir) { + SpeedLimitManager.Instance.SetSpeedLimit(otherSegmentId, laneInfo.m_finalDirection, speedLimitToSet); + } + + return true; + }); + } + } + + guiColor.a = 1f; + GUI.color = guiColor; + } + } + return hovered; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs index 40fca7050..e5033c9dc 100644 --- a/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs +++ b/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs @@ -1,389 +1,383 @@ -using ColossalFramework; -using ColossalFramework.Math; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Custom.AI; -using TrafficManager.State; -using TrafficManager.Geometry; -using TrafficManager.TrafficLight; -using UnityEngine; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using CSUtil.Commons; -using TrafficManager.Manager.Impl; -using TrafficManager.Geometry.Impl; -using ColossalFramework.UI; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -namespace TrafficManager.UI.SubTools { - public class TimedTrafficLightsTool : SubTool { - private readonly GUIStyle _counterStyle = new GUIStyle(); - private readonly int[] _hoveredButton = new int[2]; +namespace TrafficManager.UI.SubTools { + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using State; + using Traffic.Data; + using TrafficLight; + using UnityEngine; + + public class TimedTrafficLightsTool : SubTool { + private readonly GUIStyle _counterStyle = new GUIStyle(); + private readonly int[] _hoveredButton = new int[2]; private bool nodeSelectionLocked = false; - private List SelectedNodeIds = new List(); - private bool _cursorInSecondaryPanel; - private Rect _windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 480, 350)); - private Rect _windowRect2 = TrafficManagerTool.MoveGUI(new Rect(0, 0, 300, 150)); - private bool _timedPanelAdd = false; - private int _timedEditStep = -1; - private ushort _hoveredNode = 0; - private bool _timedShowNumbers = false; - private int _timedViewedStep = -1; - private int _stepMinValue = 1; - private int _stepMaxValue = 1; - private StepChangeMetric _stepMetric = StepChangeMetric.Default; - private float _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; - private string _stepMinValueStr = "1"; - private string _stepMaxValueStr = "1"; - private bool timedLightActive = false; - private int currentStep = -1; - private int numSteps = 0; - private bool inTestMode = false; - private ushort nodeIdToCopy = 0; - private HashSet currentTimedNodeIds; - - private GUIStyle layout = new GUIStyle { normal = { textColor = new Color(1f, 1f, 1f) } }; - private GUIStyle layoutRed = new GUIStyle { normal = { textColor = new Color(1f, 0f, 0f) } }; - private GUIStyle layoutGreen = new GUIStyle { normal = { textColor = new Color(0f, 1f, 0f) } }; - private GUIStyle layoutYellow = new GUIStyle { normal = { textColor = new Color(1f, 1f, 0f) } }; - - public TimedTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { - currentTimedNodeIds = new HashSet(); - } - - public override bool IsCursorInPanel() { - return base.IsCursorInPanel() || _cursorInSecondaryPanel; - } - - private void RefreshCurrentTimedNodeIds(ushort forceNodeId=0) { - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - if (forceNodeId == 0) { - currentTimedNodeIds.Clear(); - } else { - currentTimedNodeIds.Remove(forceNodeId); - } - - for (uint nodeId = (forceNodeId == 0 ? 1u : forceNodeId); nodeId <= (forceNodeId == 0 ? NetManager.MAX_NODE_COUNT - 1 : forceNodeId); ++nodeId) { - if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { - continue; - } - - if (tlsMan.HasTimedSimulation((ushort)nodeId)) { - currentTimedNodeIds.Add((ushort)nodeId); - } - } - } - - public override void OnActivate() { - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - RefreshCurrentTimedNodeIds(); - - nodeSelectionLocked = false; - foreach (ushort nodeId in currentTimedNodeIds) { - if (!Constants.ServiceFactory.NetService.IsNodeValid(nodeId)) { - continue; - } - - tlsMan.TrafficLightSimulations[nodeId].Housekeeping(); - } - } - - public override void OnSecondaryClickOverlay() { - if (!IsCursorInPanel()) { - Cleanup(); - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - } - } - - public override void OnPrimaryClickOverlay() { - if (HoveredNodeId <= 0 || nodeSelectionLocked) - return; - - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - switch (MainTool.GetToolMode()) { - case ToolMode.TimedLightsSelectNode: - case ToolMode.TimedLightsShowLights: - if (MainTool.GetToolMode() == ToolMode.TimedLightsShowLights) { - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - ClearSelectedNodes(); - } - - if (! tlsMan.HasTimedSimulation(HoveredNodeId)) { - if (IsNodeSelected(HoveredNodeId)) { - RemoveSelectedNode(HoveredNodeId); - } else { - AddSelectedNode(HoveredNodeId); - } - } else { - if (SelectedNodeIds.Count == 0) { - //timedSim.housekeeping(); - var timedLight = tlsMan.TrafficLightSimulations[HoveredNodeId].timedLight; - - if (timedLight != null) { - SelectedNodeIds = new List(timedLight.NodeGroup); - MainTool.SetToolMode(ToolMode.TimedLightsShowLights); - } - } else { - MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); - } - } - break; - case ToolMode.TimedLightsAddNode: - if (SelectedNodeIds.Count <= 0) { - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - return; - } - - if (SelectedNodeIds.Contains(HoveredNodeId)) - return; - - //bool mayEnterBlocked = Options.mayEnterBlockedJunctions; - ITimedTrafficLights existingTimedLight = null; - foreach (var nodeId in SelectedNodeIds) { - if (!tlsMan.HasTimedSimulation(nodeId)) { - continue; - } - - //mayEnterBlocked = timedNode.vehiclesMayEnterBlockedJunctions; - existingTimedLight = tlsMan.TrafficLightSimulations[nodeId].timedLight; - } - - /*if (timedSim2 != null) - timedSim2.housekeeping();*/ - ITimedTrafficLights timedLight2 = null; - if (! tlsMan.HasTimedSimulation(HoveredNodeId)) { - var nodeGroup = new List(); - nodeGroup.Add(HoveredNodeId); - tlsMan.SetUpTimedTrafficLight(HoveredNodeId, nodeGroup); - } - timedLight2 = tlsMan.TrafficLightSimulations[HoveredNodeId].timedLight; - - timedLight2.Join(existingTimedLight); - ClearSelectedNodes(); - foreach (ushort nodeId in timedLight2.NodeGroup) { - RefreshCurrentTimedNodeIds(nodeId); - AddSelectedNode(nodeId); - } - MainTool.SetToolMode(ToolMode.TimedLightsShowLights); - break; - case ToolMode.TimedLightsRemoveNode: - if (SelectedNodeIds.Count <= 0) { - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - return; - } - - if (SelectedNodeIds.Contains(HoveredNodeId)) { - tlsMan.RemoveNodeFromSimulation(HoveredNodeId, false, false); - RefreshCurrentTimedNodeIds(HoveredNodeId); - } - RemoveSelectedNode(HoveredNodeId); - MainTool.SetToolMode(ToolMode.TimedLightsShowLights); - break; - case ToolMode.TimedLightsCopyLights: - if (nodeIdToCopy == 0 || !tlsMan.HasTimedSimulation(nodeIdToCopy)) { - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - return; - } - - // compare geometry - int numSourceSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); - int numTargetSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); - - if (numSourceSegments != numTargetSegments) { - MainTool.ShowTooltip(Translation.GetString("The_chosen_traffic_light_program_is_incompatible_to_this_junction")); - return; - } - - // check for existing simulation - if (tlsMan.HasTimedSimulation(HoveredNodeId)) { - MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); - return; - } - - ITimedTrafficLights sourceTimedLights = tlsMan.TrafficLightSimulations[nodeIdToCopy].timedLight; - - // copy `nodeIdToCopy` to `HoveredNodeId` - tlsMan.SetUpTimedTrafficLight(HoveredNodeId, new List { HoveredNodeId }); - - tlsMan.TrafficLightSimulations[HoveredNodeId].timedLight.PasteSteps(sourceTimedLights); - RefreshCurrentTimedNodeIds(HoveredNodeId); - - Cleanup(); - AddSelectedNode(HoveredNodeId); - MainTool.SetToolMode(ToolMode.TimedLightsShowLights); - break; - } - } - - public override void OnToolGUI(Event e) { - base.OnToolGUI(e); - - switch (MainTool.GetToolMode()) { - case ToolMode.TimedLightsSelectNode: - _guiTimedTrafficLightsNode(); - break; - case ToolMode.TimedLightsShowLights: - case ToolMode.TimedLightsAddNode: - case ToolMode.TimedLightsRemoveNode: - _guiTimedTrafficLights(); - break; - case ToolMode.TimedLightsCopyLights: - _guiTimedTrafficLightsCopy(); - break; - } - } - - public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { - bool onlySelected = MainTool.GetToolMode() == ToolMode.TimedLightsRemoveNode; - //Log._Debug($"nodeSelLocked={nodeSelectionLocked} HoveredNodeId={HoveredNodeId} IsNodeSelected={IsNodeSelected(HoveredNodeId)} onlySelected={onlySelected} isinsideui={MainTool.GetToolController().IsInsideUI} cursorVis={Cursor.visible}"); + private List SelectedNodeIds = new List(); + private bool _cursorInSecondaryPanel; + private Rect _windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 480, 350)); + private Rect _windowRect2 = TrafficManagerTool.MoveGUI(new Rect(0, 0, 300, 150)); + private bool _timedPanelAdd = false; + private int _timedEditStep = -1; + private ushort _hoveredNode = 0; + private bool _timedShowNumbers = false; + private int _timedViewedStep = -1; + private int _stepMinValue = 1; + private int _stepMaxValue = 1; + private StepChangeMetric _stepMetric = StepChangeMetric.Default; + private float _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; + private string _stepMinValueStr = "1"; + private string _stepMaxValueStr = "1"; + private bool timedLightActive = false; + private int currentStep = -1; + private int numSteps = 0; + private bool inTestMode = false; + private ushort nodeIdToCopy = 0; + private HashSet currentTimedNodeIds; + + private GUIStyle layout = new GUIStyle { normal = { textColor = new Color(1f, 1f, 1f) } }; + private GUIStyle layoutRed = new GUIStyle { normal = { textColor = new Color(1f, 0f, 0f) } }; + private GUIStyle layoutGreen = new GUIStyle { normal = { textColor = new Color(0f, 1f, 0f) } }; + private GUIStyle layoutYellow = new GUIStyle { normal = { textColor = new Color(1f, 1f, 0f) } }; + + public TimedTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { + currentTimedNodeIds = new HashSet(); + } + + public override bool IsCursorInPanel() { + return base.IsCursorInPanel() || _cursorInSecondaryPanel; + } + + private void RefreshCurrentTimedNodeIds(ushort forceNodeId=0) { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + if (forceNodeId == 0) { + currentTimedNodeIds.Clear(); + } else { + currentTimedNodeIds.Remove(forceNodeId); + } + + for (uint nodeId = (forceNodeId == 0 ? 1u : forceNodeId); nodeId <= (forceNodeId == 0 ? NetManager.MAX_NODE_COUNT - 1 : forceNodeId); ++nodeId) { + if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { + continue; + } + + if (tlsMan.HasTimedSimulation((ushort)nodeId)) { + currentTimedNodeIds.Add((ushort)nodeId); + } + } + } + + public override void OnActivate() { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + RefreshCurrentTimedNodeIds(); + + nodeSelectionLocked = false; + foreach (ushort nodeId in currentTimedNodeIds) { + if (!Constants.ServiceFactory.NetService.IsNodeValid(nodeId)) { + continue; + } + + tlsMan.TrafficLightSimulations[nodeId].Housekeeping(); + } + } + + public override void OnSecondaryClickOverlay() { + if (!IsCursorInPanel()) { + Cleanup(); + MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); + } + } + + public override void OnPrimaryClickOverlay() { + if (HoveredNodeId <= 0 || nodeSelectionLocked) + return; + + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + switch (MainTool.GetToolMode()) { + case ToolMode.TimedLightsSelectNode: + case ToolMode.TimedLightsShowLights: + if (MainTool.GetToolMode() == ToolMode.TimedLightsShowLights) { + MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); + ClearSelectedNodes(); + } + + if (! tlsMan.HasTimedSimulation(HoveredNodeId)) { + if (IsNodeSelected(HoveredNodeId)) { + RemoveSelectedNode(HoveredNodeId); + } else { + AddSelectedNode(HoveredNodeId); + } + } else { + if (SelectedNodeIds.Count == 0) { + //timedSim.housekeeping(); + var timedLight = tlsMan.TrafficLightSimulations[HoveredNodeId].timedLight; + + if (timedLight != null) { + SelectedNodeIds = new List(timedLight.NodeGroup); + MainTool.SetToolMode(ToolMode.TimedLightsShowLights); + } + } else { + MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); + } + } + break; + case ToolMode.TimedLightsAddNode: + if (SelectedNodeIds.Count <= 0) { + MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); + return; + } + + if (SelectedNodeIds.Contains(HoveredNodeId)) + return; + + //bool mayEnterBlocked = Options.mayEnterBlockedJunctions; + ITimedTrafficLights existingTimedLight = null; + foreach (var nodeId in SelectedNodeIds) { + if (!tlsMan.HasTimedSimulation(nodeId)) { + continue; + } + + //mayEnterBlocked = timedNode.vehiclesMayEnterBlockedJunctions; + existingTimedLight = tlsMan.TrafficLightSimulations[nodeId].timedLight; + } + + /*if (timedSim2 != null) + timedSim2.housekeeping();*/ + ITimedTrafficLights timedLight2 = null; + if (! tlsMan.HasTimedSimulation(HoveredNodeId)) { + var nodeGroup = new List(); + nodeGroup.Add(HoveredNodeId); + tlsMan.SetUpTimedTrafficLight(HoveredNodeId, nodeGroup); + } + timedLight2 = tlsMan.TrafficLightSimulations[HoveredNodeId].timedLight; + + timedLight2.Join(existingTimedLight); + ClearSelectedNodes(); + foreach (ushort nodeId in timedLight2.NodeGroup) { + RefreshCurrentTimedNodeIds(nodeId); + AddSelectedNode(nodeId); + } + MainTool.SetToolMode(ToolMode.TimedLightsShowLights); + break; + case ToolMode.TimedLightsRemoveNode: + if (SelectedNodeIds.Count <= 0) { + MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); + return; + } + + if (SelectedNodeIds.Contains(HoveredNodeId)) { + tlsMan.RemoveNodeFromSimulation(HoveredNodeId, false, false); + RefreshCurrentTimedNodeIds(HoveredNodeId); + } + RemoveSelectedNode(HoveredNodeId); + MainTool.SetToolMode(ToolMode.TimedLightsShowLights); + break; + case ToolMode.TimedLightsCopyLights: + if (nodeIdToCopy == 0 || !tlsMan.HasTimedSimulation(nodeIdToCopy)) { + MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); + return; + } + + // compare geometry + int numSourceSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); + int numTargetSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); + + if (numSourceSegments != numTargetSegments) { + MainTool.ShowTooltip(Translation.GetString("The_chosen_traffic_light_program_is_incompatible_to_this_junction")); + return; + } + + // check for existing simulation + if (tlsMan.HasTimedSimulation(HoveredNodeId)) { + MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); + return; + } + + ITimedTrafficLights sourceTimedLights = tlsMan.TrafficLightSimulations[nodeIdToCopy].timedLight; + + // copy `nodeIdToCopy` to `HoveredNodeId` + tlsMan.SetUpTimedTrafficLight(HoveredNodeId, new List { HoveredNodeId }); + + tlsMan.TrafficLightSimulations[HoveredNodeId].timedLight.PasteSteps(sourceTimedLights); + RefreshCurrentTimedNodeIds(HoveredNodeId); + + Cleanup(); + AddSelectedNode(HoveredNodeId); + MainTool.SetToolMode(ToolMode.TimedLightsShowLights); + break; + } + } + + public override void OnToolGUI(Event e) { + base.OnToolGUI(e); + + switch (MainTool.GetToolMode()) { + case ToolMode.TimedLightsSelectNode: + _guiTimedTrafficLightsNode(); + break; + case ToolMode.TimedLightsShowLights: + case ToolMode.TimedLightsAddNode: + case ToolMode.TimedLightsRemoveNode: + _guiTimedTrafficLights(); + break; + case ToolMode.TimedLightsCopyLights: + _guiTimedTrafficLightsCopy(); + break; + } + } + + public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { + bool onlySelected = MainTool.GetToolMode() == ToolMode.TimedLightsRemoveNode; + //Log._Debug($"nodeSelLocked={nodeSelectionLocked} HoveredNodeId={HoveredNodeId} IsNodeSelected={IsNodeSelected(HoveredNodeId)} onlySelected={onlySelected} isinsideui={MainTool.GetToolController().IsInsideUI} cursorVis={Cursor.visible}"); if (!nodeSelectionLocked && - HoveredNodeId != 0 && - (!IsNodeSelected(HoveredNodeId) ^ onlySelected) && - !MainTool.GetToolController().IsInsideUI && - Cursor.visible && - Flags.mayHaveTrafficLight(HoveredNodeId) - ) { - MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, false, false); - } - - if (SelectedNodeIds.Count <= 0) return; - - foreach (var index in SelectedNodeIds) { - MainTool.DrawNodeCircle(cameraInfo, index, true, false); - } - } - - private void _guiTimedControlPanel(int num) { - //Log._Debug("guiTimedControlPanel"); - try { - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - if (MainTool.GetToolMode() == ToolMode.TimedLightsAddNode || MainTool.GetToolMode() == ToolMode.TimedLightsRemoveNode) { - GUILayout.Label(Translation.GetString("Select_junction")); - if (GUILayout.Button(Translation.GetString("Cancel"))) { - MainTool.SetToolMode(ToolMode.TimedLightsShowLights); - } else { - DragWindow(ref _windowRect); - return; - } - } - - if (! tlsMan.HasTimedSimulation(SelectedNodeIds[0])) { - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - //Log._Debug("nodesim or timednodemain is null"); - DragWindow(ref _windowRect); - return; - } - - var timedNodeMain = tlsMan.TrafficLightSimulations[SelectedNodeIds[0]].timedLight; - - if (Event.current.type == EventType.Layout) { - timedLightActive = tlsMan.HasActiveTimedSimulation(SelectedNodeIds[0]); - currentStep = timedNodeMain.CurrentStep; - inTestMode = timedNodeMain.IsInTestMode(); - numSteps = timedNodeMain.NumSteps(); - } - - if (!timedLightActive && numSteps > 0 && !_timedPanelAdd && _timedEditStep < 0 && _timedViewedStep < 0) { - _timedViewedStep = 0; - foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.GetStep(_timedViewedStep).UpdateLiveLights(true); - } - } - - for (var i = 0; i < timedNodeMain.NumSteps(); i++) { - GUILayout.BeginHorizontal(); - - if (_timedEditStep != i) { - if (timedLightActive) { - if (i == currentStep) { - GUILayout.BeginVertical(); - GUILayout.Space(5); - String labelStr = Translation.GetString("State") + " " + (i + 1) + ": (" + Translation.GetString("min/max") + ")" + timedNodeMain.GetStep(i).MinTimeRemaining() + "/" + timedNodeMain.GetStep(i).MaxTimeRemaining(); - float flow = Single.NaN; - float wait = Single.NaN; - if (inTestMode) { - try { - timedNodeMain.GetStep(timedNodeMain.CurrentStep).CalcWaitFlow(true, timedNodeMain.CurrentStep, out wait, out flow); - } catch (Exception e) { - Log.Warning("calcWaitFlow in UI: This is not thread-safe: " + e.ToString()); - } - } else { - wait = timedNodeMain.GetStep(i).CurrentWait; - flow = timedNodeMain.GetStep(i).CurrentFlow; - } - if (!Single.IsNaN(flow) && !Single.IsNaN(wait)) - labelStr += " " + Translation.GetString("avg._flow") + ": " + String.Format("{0:0.##}", flow) + " " + Translation.GetString("avg._wait") + ": " + String.Format("{0:0.##}", wait); - GUIStyle labelLayout = layout; - if (inTestMode && !Single.IsNaN(wait) && !Single.IsNaN(flow)) { - float metric; - if (timedNodeMain.GetStep(i).ShouldGoToNextStep(flow, wait, out metric)) - labelLayout = layoutRed; - else - labelLayout = layoutGreen; - } else { - bool inEndTransition = false; - try { - inEndTransition = timedNodeMain.GetStep(i).IsInEndTransition(); - } catch (Exception e) { - Log.Error("Error while determining if timed traffic light is in end transition: " + e.ToString()); - } - labelLayout = inEndTransition ? layoutYellow : layoutGreen; - } - GUILayout.Label(labelStr, labelLayout); - GUILayout.Space(5); - GUILayout.EndVertical(); - if (GUILayout.Button(Translation.GetString("Skip"), GUILayout.Width(80))) { - foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.SkipStep(); - } - } - } else { - GUILayout.Label(Translation.GetString("State") + " " + (i + 1) + ": " + timedNodeMain.GetStep(i).MinTime + " - " + timedNodeMain.GetStep(i).MaxTime, layout); - } - } else { - GUIStyle labelLayout = layout; - if (_timedViewedStep == i) { - labelLayout = layoutGreen; - } - GUILayout.Label(Translation.GetString("State") + " " + (i + 1) + ": " + timedNodeMain.GetStep(i).MinTime + " - " + timedNodeMain.GetStep(i).MaxTime, labelLayout); - - if (_timedEditStep < 0) { - GUILayout.BeginHorizontal(GUILayout.Width(100)); - - if (i > 0) { - if (GUILayout.Button(Translation.GetString("up"), GUILayout.Width(48))) { - foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.MoveStep(i, i - 1); - } + HoveredNodeId != 0 && + (!IsNodeSelected(HoveredNodeId) ^ onlySelected) && + !MainTool.GetToolController().IsInsideUI && + Cursor.visible && + Flags.mayHaveTrafficLight(HoveredNodeId) + ) { + MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, false, false); + } + + if (SelectedNodeIds.Count <= 0) return; + + foreach (var index in SelectedNodeIds) { + MainTool.DrawNodeCircle(cameraInfo, index, true, false); + } + } + + private void _guiTimedControlPanel(int num) { + //Log._Debug("guiTimedControlPanel"); + try { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + if (MainTool.GetToolMode() == ToolMode.TimedLightsAddNode || MainTool.GetToolMode() == ToolMode.TimedLightsRemoveNode) { + GUILayout.Label(Translation.GetString("Select_junction")); + if (GUILayout.Button(Translation.GetString("Cancel"))) { + MainTool.SetToolMode(ToolMode.TimedLightsShowLights); + } else { + DragWindow(ref _windowRect); + return; + } + } + + if (! tlsMan.HasTimedSimulation(SelectedNodeIds[0])) { + MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); + //Log._Debug("nodesim or timednodemain is null"); + DragWindow(ref _windowRect); + return; + } + + var timedNodeMain = tlsMan.TrafficLightSimulations[SelectedNodeIds[0]].timedLight; + + if (Event.current.type == EventType.Layout) { + timedLightActive = tlsMan.HasActiveTimedSimulation(SelectedNodeIds[0]); + currentStep = timedNodeMain.CurrentStep; + inTestMode = timedNodeMain.IsInTestMode(); + numSteps = timedNodeMain.NumSteps(); + } + + if (!timedLightActive && numSteps > 0 && !_timedPanelAdd && _timedEditStep < 0 && _timedViewedStep < 0) { + _timedViewedStep = 0; + foreach (var nodeId in SelectedNodeIds) { + tlsMan.TrafficLightSimulations[nodeId].timedLight?.GetStep(_timedViewedStep).UpdateLiveLights(true); + } + } + + for (var i = 0; i < timedNodeMain.NumSteps(); i++) { + GUILayout.BeginHorizontal(); + + if (_timedEditStep != i) { + if (timedLightActive) { + if (i == currentStep) { + GUILayout.BeginVertical(); + GUILayout.Space(5); + String labelStr = Translation.GetString("State") + " " + (i + 1) + ": (" + Translation.GetString("min/max") + ")" + timedNodeMain.GetStep(i).MinTimeRemaining() + "/" + timedNodeMain.GetStep(i).MaxTimeRemaining(); + float flow = Single.NaN; + float wait = Single.NaN; + if (inTestMode) { + try { + timedNodeMain.GetStep(timedNodeMain.CurrentStep).CalcWaitFlow(true, timedNodeMain.CurrentStep, out wait, out flow); + } catch (Exception e) { + Log.Warning("calcWaitFlow in UI: This is not thread-safe: " + e.ToString()); + } + } else { + wait = timedNodeMain.GetStep(i).CurrentWait; + flow = timedNodeMain.GetStep(i).CurrentFlow; + } + if (!Single.IsNaN(flow) && !Single.IsNaN(wait)) + labelStr += " " + Translation.GetString("avg._flow") + ": " + String.Format("{0:0.##}", flow) + " " + Translation.GetString("avg._wait") + ": " + String.Format("{0:0.##}", wait); + GUIStyle labelLayout = layout; + if (inTestMode && !Single.IsNaN(wait) && !Single.IsNaN(flow)) { + float metric; + if (timedNodeMain.GetStep(i).ShouldGoToNextStep(flow, wait, out metric)) + labelLayout = layoutRed; + else + labelLayout = layoutGreen; + } else { + bool inEndTransition = false; + try { + inEndTransition = timedNodeMain.GetStep(i).IsInEndTransition(); + } catch (Exception e) { + Log.Error("Error while determining if timed traffic light is in end transition: " + e.ToString()); + } + labelLayout = inEndTransition ? layoutYellow : layoutGreen; + } + GUILayout.Label(labelStr, labelLayout); + GUILayout.Space(5); + GUILayout.EndVertical(); + if (GUILayout.Button(Translation.GetString("Skip"), GUILayout.Width(80))) { + foreach (var nodeId in SelectedNodeIds) { + tlsMan.TrafficLightSimulations[nodeId].timedLight?.SkipStep(); + } + } + } else { + GUILayout.Label(Translation.GetString("State") + " " + (i + 1) + ": " + timedNodeMain.GetStep(i).MinTime + " - " + timedNodeMain.GetStep(i).MaxTime, layout); + } + } else { + GUIStyle labelLayout = layout; + if (_timedViewedStep == i) { + labelLayout = layoutGreen; + } + GUILayout.Label(Translation.GetString("State") + " " + (i + 1) + ": " + timedNodeMain.GetStep(i).MinTime + " - " + timedNodeMain.GetStep(i).MaxTime, labelLayout); + + if (_timedEditStep < 0) { + GUILayout.BeginHorizontal(GUILayout.Width(100)); + + if (i > 0) { + if (GUILayout.Button(Translation.GetString("up"), GUILayout.Width(48))) { + foreach (var nodeId in SelectedNodeIds) { + tlsMan.TrafficLightSimulations[nodeId].timedLight?.MoveStep(i, i - 1); + } _timedViewedStep = i - 1; } - } else { - GUILayout.Space(50); - } - - if (i < numSteps - 1) { - if (GUILayout.Button(Translation.GetString("down"), GUILayout.Width(48))) { - foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.MoveStep(i, i + 1); - } + } else { + GUILayout.Space(50); + } + + if (i < numSteps - 1) { + if (GUILayout.Button(Translation.GetString("down"), GUILayout.Width(48))) { + foreach (var nodeId in SelectedNodeIds) { + tlsMan.TrafficLightSimulations[nodeId].timedLight?.MoveStep(i, i + 1); + } _timedViewedStep = i + 1; } - } else { - GUILayout.Space(50); - } + } else { + GUILayout.Space(50); + } - GUILayout.EndHorizontal(); + GUILayout.EndHorizontal(); GUI.color = Color.red; if (GUILayout.Button(Translation.GetString("Delete"), GUILayout.Width(70))) { - RemoveStep(i); + RemoveStep(i); } GUI.color = Color.white; @@ -413,240 +407,240 @@ private void _guiTimedControlPanel(int num) { } } } - } - } else { - nodeSelectionLocked = true; - int oldStepMinValue = _stepMinValue; - int oldStepMaxValue = _stepMaxValue; - - // Editing step - GUILayout.Label(Translation.GetString("Min._Time:"), GUILayout.Width(75)); - _stepMinValueStr = GUILayout.TextField(_stepMinValueStr, GUILayout.Height(20)); - if (!Int32.TryParse(_stepMinValueStr, out _stepMinValue)) - _stepMinValue = oldStepMinValue; - - GUILayout.Label(Translation.GetString("Max._Time:"), GUILayout.Width(75)); - _stepMaxValueStr = GUILayout.TextField(_stepMaxValueStr, GUILayout.Height(20)); - if (!Int32.TryParse(_stepMaxValueStr, out _stepMaxValue)) - _stepMaxValue = oldStepMaxValue; - - if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { - if (_stepMinValue < 0) - _stepMinValue = 0; - if (_stepMaxValue <= 0) - _stepMaxValue = 1; - if (_stepMaxValue < _stepMinValue) - _stepMaxValue = _stepMinValue; - if (_waitFlowBalance <= 0) - _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; - - foreach (var nodeId in SelectedNodeIds) { - var step = tlsMan.TrafficLightSimulations[nodeId].timedLight?.GetStep(_timedEditStep); - - if (step != null) { - step.MinTime = _stepMinValue; - step.MaxTime = _stepMaxValue; - step.ChangeMetric = _stepMetric; - step.WaitFlowBalance = _waitFlowBalance; - step.UpdateLights(); - } - } - - _timedViewedStep = _timedEditStep; - _timedEditStep = -1; - nodeSelectionLocked = false; - } - - GUILayout.EndHorizontal(); - - BuildStepChangeMetricDisplay(true); - BuildFlowPolicyDisplay(true); - GUILayout.BeginHorizontal(); - } - - GUILayout.EndHorizontal(); - } // foreach step - - GUILayout.BeginHorizontal(); - - if (_timedEditStep < 0 && !timedLightActive) { - if (_timedPanelAdd) { - nodeSelectionLocked = true; - // new step - int oldStepMinValue = _stepMinValue; - int oldStepMaxValue = _stepMaxValue; - - GUILayout.Label(Translation.GetString("Min._Time:"), GUILayout.Width(65)); - _stepMinValueStr = GUILayout.TextField(_stepMinValueStr, GUILayout.Height(20)); - if (!Int32.TryParse(_stepMinValueStr, out _stepMinValue)) - _stepMinValue = oldStepMinValue; - - GUILayout.Label(Translation.GetString("Max._Time:"), GUILayout.Width(65)); - _stepMaxValueStr = GUILayout.TextField(_stepMaxValueStr, GUILayout.Height(20)); - if (!Int32.TryParse(_stepMaxValueStr, out _stepMaxValue)) - _stepMaxValue = oldStepMaxValue; - + } + } else { + nodeSelectionLocked = true; + int oldStepMinValue = _stepMinValue; + int oldStepMaxValue = _stepMaxValue; + + // Editing step + GUILayout.Label(Translation.GetString("Min._Time:"), GUILayout.Width(75)); + _stepMinValueStr = GUILayout.TextField(_stepMinValueStr, GUILayout.Height(20)); + if (!Int32.TryParse(_stepMinValueStr, out _stepMinValue)) + _stepMinValue = oldStepMinValue; + + GUILayout.Label(Translation.GetString("Max._Time:"), GUILayout.Width(75)); + _stepMaxValueStr = GUILayout.TextField(_stepMaxValueStr, GUILayout.Height(20)); + if (!Int32.TryParse(_stepMaxValueStr, out _stepMaxValue)) + _stepMaxValue = oldStepMaxValue; + + if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { + if (_stepMinValue < 0) + _stepMinValue = 0; + if (_stepMaxValue <= 0) + _stepMaxValue = 1; + if (_stepMaxValue < _stepMinValue) + _stepMaxValue = _stepMinValue; + if (_waitFlowBalance <= 0) + _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; + + foreach (var nodeId in SelectedNodeIds) { + var step = tlsMan.TrafficLightSimulations[nodeId].timedLight?.GetStep(_timedEditStep); + + if (step != null) { + step.MinTime = _stepMinValue; + step.MaxTime = _stepMaxValue; + step.ChangeMetric = _stepMetric; + step.WaitFlowBalance = _waitFlowBalance; + step.UpdateLights(); + } + } + + _timedViewedStep = _timedEditStep; + _timedEditStep = -1; + nodeSelectionLocked = false; + } + + GUILayout.EndHorizontal(); + + BuildStepChangeMetricDisplay(true); + BuildFlowPolicyDisplay(true); + GUILayout.BeginHorizontal(); + } + + GUILayout.EndHorizontal(); + } // foreach step + + GUILayout.BeginHorizontal(); + + if (_timedEditStep < 0 && !timedLightActive) { + if (_timedPanelAdd) { + nodeSelectionLocked = true; + // new step + int oldStepMinValue = _stepMinValue; + int oldStepMaxValue = _stepMaxValue; + + GUILayout.Label(Translation.GetString("Min._Time:"), GUILayout.Width(65)); + _stepMinValueStr = GUILayout.TextField(_stepMinValueStr, GUILayout.Height(20)); + if (!Int32.TryParse(_stepMinValueStr, out _stepMinValue)) + _stepMinValue = oldStepMinValue; + + GUILayout.Label(Translation.GetString("Max._Time:"), GUILayout.Width(65)); + _stepMaxValueStr = GUILayout.TextField(_stepMaxValueStr, GUILayout.Height(20)); + if (!Int32.TryParse(_stepMaxValueStr, out _stepMaxValue)) + _stepMaxValue = oldStepMaxValue; + if (GUILayout.Button("X", GUILayout.Width(22))) { _timedPanelAdd = false; } if (GUILayout.Button(Translation.GetString("Add"), GUILayout.Width(70))) { - TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddStep"); - if (_stepMinValue < 0) - _stepMinValue = 0; - if (_stepMaxValue <= 0) - _stepMaxValue = 1; - if (_stepMaxValue < _stepMinValue) - _stepMaxValue = _stepMinValue; - if (_waitFlowBalance <= 0) - _waitFlowBalance = 1f; - - foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.AddStep(_stepMinValue, _stepMaxValue, _stepMetric, _waitFlowBalance); - } - - _timedPanelAdd = false; - _timedViewedStep = timedNodeMain.NumSteps() - 1; - } - - GUILayout.EndHorizontal(); - - BuildStepChangeMetricDisplay(true); - BuildFlowPolicyDisplay(true); - GUILayout.BeginHorizontal(); - - } else { - if (_timedEditStep < 0) { - if (GUILayout.Button(Translation.GetString("Add_step"))) { - TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddStep"); - _timedPanelAdd = true; - nodeSelectionLocked = true; - _timedViewedStep = -1; - _timedEditStep = -1; + TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddStep"); + if (_stepMinValue < 0) + _stepMinValue = 0; + if (_stepMaxValue <= 0) + _stepMaxValue = 1; + if (_stepMaxValue < _stepMinValue) + _stepMaxValue = _stepMinValue; + if (_waitFlowBalance <= 0) + _waitFlowBalance = 1f; + + foreach (var nodeId in SelectedNodeIds) { + tlsMan.TrafficLightSimulations[nodeId].timedLight?.AddStep(_stepMinValue, _stepMaxValue, _stepMetric, _waitFlowBalance); + } + + _timedPanelAdd = false; + _timedViewedStep = timedNodeMain.NumSteps() - 1; + } + + GUILayout.EndHorizontal(); + + BuildStepChangeMetricDisplay(true); + BuildFlowPolicyDisplay(true); + GUILayout.BeginHorizontal(); + + } else { + if (_timedEditStep < 0) { + if (GUILayout.Button(Translation.GetString("Add_step"))) { + TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddStep"); + _timedPanelAdd = true; + nodeSelectionLocked = true; + _timedViewedStep = -1; + _timedEditStep = -1; _stepMetric = StepChangeMetric.Default; } - } - } - } - - GUILayout.EndHorizontal(); - - GUILayout.Space(5); - - if (numSteps > 1 && _timedEditStep < 0) { - if (timedLightActive) { - if (GUILayout.Button(_timedShowNumbers ? Translation.GetString("Hide_counters") : Translation.GetString("Show_counters"))) { - _timedShowNumbers = !_timedShowNumbers; - } - - if (GUILayout.Button(Translation.GetString("Stop"))) { - foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.Stop(); - } - } - - /*bool isInTestMode = false; - foreach (var sim in SelectedNodeIndexes.Select(tlsMan.GetNodeSimulation)) { - if (sim.TimedLight.IsInTestMode()) { - isInTestMode = true; - break; - } - }*/ - - - var curStep = timedNodeMain.CurrentStep; - ITimedTrafficLightsStep currentStep = timedNodeMain.GetStep(curStep); - _stepMetric = currentStep.ChangeMetric; - if (currentStep.MaxTime > currentStep.MinTime) { - BuildStepChangeMetricDisplay(false); - } - - _waitFlowBalance = timedNodeMain.GetStep(curStep).WaitFlowBalance; - BuildFlowPolicyDisplay(inTestMode); - foreach (var nodeId in SelectedNodeIds) { - var step = tlsMan.TrafficLightSimulations[nodeId].timedLight?.GetStep(curStep); - if (step != null) { - step.WaitFlowBalance = _waitFlowBalance; - } - } - - //var mayEnterIfBlocked = GUILayout.Toggle(timedNodeMain.vehiclesMayEnterBlockedJunctions, Translation.GetString("Vehicles_may_enter_blocked_junctions"), new GUILayoutOption[] { }); - var testMode = GUILayout.Toggle(inTestMode, Translation.GetString("Enable_test_mode_(stay_in_current_step)"), new GUILayoutOption[] { }); - foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.SetTestMode(testMode); - } - } else { - if (_timedEditStep < 0 && !_timedPanelAdd) { - if (GUILayout.Button(Translation.GetString("Start"))) { - _timedPanelAdd = false; + } + } + } + + GUILayout.EndHorizontal(); + + GUILayout.Space(5); + + if (numSteps > 1 && _timedEditStep < 0) { + if (timedLightActive) { + if (GUILayout.Button(_timedShowNumbers ? Translation.GetString("Hide_counters") : Translation.GetString("Show_counters"))) { + _timedShowNumbers = !_timedShowNumbers; + } + + if (GUILayout.Button(Translation.GetString("Stop"))) { + foreach (var nodeId in SelectedNodeIds) { + tlsMan.TrafficLightSimulations[nodeId].timedLight?.Stop(); + } + } + + /*bool isInTestMode = false; + foreach (var sim in SelectedNodeIndexes.Select(tlsMan.GetNodeSimulation)) { + if (sim.TimedLight.IsInTestMode()) { + isInTestMode = true; + break; + } + }*/ + + + var curStep = timedNodeMain.CurrentStep; + ITimedTrafficLightsStep currentStep = timedNodeMain.GetStep(curStep); + _stepMetric = currentStep.ChangeMetric; + if (currentStep.MaxTime > currentStep.MinTime) { + BuildStepChangeMetricDisplay(false); + } + + _waitFlowBalance = timedNodeMain.GetStep(curStep).WaitFlowBalance; + BuildFlowPolicyDisplay(inTestMode); + foreach (var nodeId in SelectedNodeIds) { + var step = tlsMan.TrafficLightSimulations[nodeId].timedLight?.GetStep(curStep); + if (step != null) { + step.WaitFlowBalance = _waitFlowBalance; + } + } + + //var mayEnterIfBlocked = GUILayout.Toggle(timedNodeMain.vehiclesMayEnterBlockedJunctions, Translation.GetString("Vehicles_may_enter_blocked_junctions"), new GUILayoutOption[] { }); + var testMode = GUILayout.Toggle(inTestMode, Translation.GetString("Enable_test_mode_(stay_in_current_step)"), new GUILayoutOption[] { }); + foreach (var nodeId in SelectedNodeIds) { + tlsMan.TrafficLightSimulations[nodeId].timedLight?.SetTestMode(testMode); + } + } else { + if (_timedEditStep < 0 && !_timedPanelAdd) { + if (GUILayout.Button(Translation.GetString("Start"))) { + _timedPanelAdd = false; nodeSelectionLocked = false; foreach (var nodeId in SelectedNodeIds) { - tlsMan.TrafficLightSimulations[nodeId].timedLight?.Start(); - } - } - } - } - } + tlsMan.TrafficLightSimulations[nodeId].timedLight?.Start(); + } + } + } + } + } - if (_timedEditStep >= 0) { - DragWindow(ref _windowRect); - return; - } + if (_timedEditStep >= 0) { + DragWindow(ref _windowRect); + return; + } - GUILayout.Space(30); + GUILayout.Space(30); - if (SelectedNodeIds.Count == 1 && timedNodeMain.NumSteps() > 0) { - GUILayout.BeginHorizontal(); + if (SelectedNodeIds.Count == 1 && timedNodeMain.NumSteps() > 0) { + GUILayout.BeginHorizontal(); - if (GUILayout.Button(Translation.GetString("Rotate_left"))) { - timedNodeMain.RotateLeft(); + if (GUILayout.Button(Translation.GetString("Rotate_left"))) { + timedNodeMain.RotateLeft(); _timedViewedStep = 0; } - if (GUILayout.Button(Translation.GetString("Copy"))) { - TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Copy"); - nodeIdToCopy = SelectedNodeIds[0]; - MainTool.SetToolMode(ToolMode.TimedLightsCopyLights); - } + if (GUILayout.Button(Translation.GetString("Copy"))) { + TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Copy"); + nodeIdToCopy = SelectedNodeIds[0]; + MainTool.SetToolMode(ToolMode.TimedLightsCopyLights); + } - if (GUILayout.Button(Translation.GetString("Rotate_right"))) { - timedNodeMain.RotateRight(); + if (GUILayout.Button(Translation.GetString("Rotate_right"))) { + timedNodeMain.RotateRight(); _timedViewedStep = 0; } - GUILayout.EndHorizontal(); - } + GUILayout.EndHorizontal(); + } - if (!timedLightActive) { - GUILayout.Space(30); + if (!timedLightActive) { + GUILayout.Space(30); if (GUILayout.Button(Translation.GetString("Add_junction_to_timed_light"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddJunction"); - MainTool.SetToolMode(ToolMode.TimedLightsAddNode); - } + MainTool.SetToolMode(ToolMode.TimedLightsAddNode); + } - if (SelectedNodeIds.Count > 1) { + if (SelectedNodeIds.Count > 1) { if (GUILayout.Button(Translation.GetString("Remove_junction_from_timed_light"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_RemoveJunction"); - MainTool.SetToolMode(ToolMode.TimedLightsRemoveNode); - } - } + MainTool.SetToolMode(ToolMode.TimedLightsRemoveNode); + } + } - GUILayout.Space(30); + GUILayout.Space(30); - if (GUILayout.Button(Translation.GetString("Remove_timed_traffic_light"))) { - DisableTimed(); - ClearSelectedNodes(); - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - } - } + if (GUILayout.Button(Translation.GetString("Remove_timed_traffic_light"))) { + DisableTimed(); + ClearSelectedNodes(); + MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); + } + } - DragWindow(ref _windowRect); - } catch (Exception e) { - Log.Error($"TimedTrafficLightsTool._guiTimedControlPanel: {e}"); - } - } + DragWindow(ref _windowRect); + } catch (Exception e) { + Log.Error($"TimedTrafficLightsTool._guiTimedControlPanel: {e}"); + } + } private void RemoveStep(int stepIndex) { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; @@ -659,1180 +653,1180 @@ private void RemoveStep(int stepIndex) { } public override void Cleanup() { - SelectedNodeId = 0; - ClearSelectedNodes(); - - _timedShowNumbers = false; - _timedPanelAdd = false; - _timedEditStep = -1; - _hoveredNode = 0; - _timedShowNumbers = false; - _timedViewedStep = -1; - timedLightActive = false; - nodeIdToCopy = 0; - } - - public override void Initialize() { - base.Initialize(); - Cleanup(); - if (Options.timedLightsOverlay) { - RefreshCurrentTimedNodeIds(); - } else { - currentTimedNodeIds.Clear(); - } - } - - private void BuildStepChangeMetricDisplay(bool editable) { - GUILayout.BeginVertical(); - - if (editable) { - GUILayout.Label(Translation.GetString("After_min._time_has_elapsed_switch_to_next_step_if") + ":"); - - if (GUILayout.Toggle(_stepMetric == StepChangeMetric.Default, GetStepChangeMetricDescription(StepChangeMetric.Default))) { - _stepMetric = StepChangeMetric.Default; - } - - if (GUILayout.Toggle(_stepMetric == StepChangeMetric.FirstFlow, GetStepChangeMetricDescription(StepChangeMetric.FirstFlow))) { - _stepMetric = StepChangeMetric.FirstFlow; - } - - if (GUILayout.Toggle(_stepMetric == StepChangeMetric.FirstWait, GetStepChangeMetricDescription(StepChangeMetric.FirstWait))) { - _stepMetric = StepChangeMetric.FirstWait; - } - - if (GUILayout.Toggle(_stepMetric == StepChangeMetric.NoFlow, GetStepChangeMetricDescription(StepChangeMetric.NoFlow))) { - _stepMetric = StepChangeMetric.NoFlow; - } - - if (GUILayout.Toggle(_stepMetric == StepChangeMetric.NoWait, GetStepChangeMetricDescription(StepChangeMetric.NoWait))) { - _stepMetric = StepChangeMetric.NoWait; - } - } else { - GUILayout.Label(Translation.GetString("Adaptive_step_switching") + ": " + GetStepChangeMetricDescription(_stepMetric)); - } - - GUILayout.EndVertical(); - } - - private void BuildFlowPolicyDisplay(bool editable) { - string formatStr; - if (_waitFlowBalance < 0.01f) - formatStr = "{0:0.###}"; - else if (_waitFlowBalance < 0.1f) - formatStr = "{0:0.##}"; - else - formatStr = "{0:0.#}"; - - GUILayout.BeginHorizontal(); - if (editable) { - GUILayout.Label(Translation.GetString("Sensitivity") + " (" + String.Format(formatStr, _waitFlowBalance) + ", " + getWaitFlowBalanceInfo() + "):"); - if (_waitFlowBalance <= 0.01f) { - if (_waitFlowBalance >= 0) { - if (GUILayout.Button("-.001")) { - _waitFlowBalance -= 0.001f; - } - } - if (_waitFlowBalance < 0.01f) { - if (GUILayout.Button("+.001")) { - _waitFlowBalance += 0.001f; - } - } - } else if (_waitFlowBalance <= 0.1f) { - if (GUILayout.Button("-.01")) { - _waitFlowBalance -= 0.01f; - } - if (_waitFlowBalance < 0.1f) { - if (GUILayout.Button("+.01")) { - _waitFlowBalance += 0.01f; - } - } - } - if (_waitFlowBalance < 0) - _waitFlowBalance = 0; - if (_waitFlowBalance > 10) - _waitFlowBalance = 10; - GUILayout.EndHorizontal(); - - _waitFlowBalance = GUILayout.HorizontalSlider(_waitFlowBalance, 0.001f, 10f); - - // step snapping - if (_waitFlowBalance < 0.001f) { - _waitFlowBalance = 0.001f; - } else if (_waitFlowBalance < 0.01f) { - _waitFlowBalance = Mathf.Round(_waitFlowBalance * 1000f) * 0.001f; - } else if (_waitFlowBalance < 0.1f) { - _waitFlowBalance = Mathf.Round(_waitFlowBalance * 100f) * 0.01f; - } else if (_waitFlowBalance < 10f) { - _waitFlowBalance = Mathf.Round(_waitFlowBalance * 10f) * 0.1f; - } else { - _waitFlowBalance = 10f; - } - - GUILayout.BeginHorizontal(); - GUIStyle style = new GUIStyle(); - style.normal.textColor = Color.white; - style.alignment = TextAnchor.LowerLeft; - GUILayout.Label(Translation.GetString("Low"), style, new GUILayoutOption[] { GUILayout.Height(10) }); - style.alignment = TextAnchor.LowerRight; - GUILayout.Label(Translation.GetString("High"), style, new GUILayoutOption[] { GUILayout.Height(10) }); - } else { - GUILayout.Label(Translation.GetString("Sensitivity") + ": " + String.Format(formatStr, _waitFlowBalance) + " (" + getWaitFlowBalanceInfo() + ")"); - } - GUILayout.EndHorizontal(); - - GUILayout.Space(5); - } - - private string GetStepChangeMetricDescription(StepChangeMetric metric) { - switch (metric) { - case StepChangeMetric.Default: - default: - return Translation.GetString("flow_ratio") + " < " + Translation.GetString("wait_ratio") + " (" + Translation.GetString("default") + ")"; - case StepChangeMetric.FirstFlow: - return Translation.GetString("flow_ratio") + " > 0"; - case StepChangeMetric.FirstWait: - return Translation.GetString("wait_ratio") + " > 0"; - case StepChangeMetric.NoFlow: - return Translation.GetString("flow_ratio") + " = 0"; - case StepChangeMetric.NoWait: - return Translation.GetString("wait_ratio") + " = 0"; - } - } - - private void _guiTimedTrafficLightsNode() { - _cursorInSecondaryPanel = false; - - _windowRect2 = GUILayout.Window(252, _windowRect2, _guiTimedTrafficLightsNodeWindow, Translation.GetString("Select_nodes_windowTitle"), WindowStyle); + SelectedNodeId = 0; + ClearSelectedNodes(); + + _timedShowNumbers = false; + _timedPanelAdd = false; + _timedEditStep = -1; + _hoveredNode = 0; + _timedShowNumbers = false; + _timedViewedStep = -1; + timedLightActive = false; + nodeIdToCopy = 0; + } + + public override void Initialize() { + base.Initialize(); + Cleanup(); + if (Options.timedLightsOverlay) { + RefreshCurrentTimedNodeIds(); + } else { + currentTimedNodeIds.Clear(); + } + } + + private void BuildStepChangeMetricDisplay(bool editable) { + GUILayout.BeginVertical(); + + if (editable) { + GUILayout.Label(Translation.GetString("After_min._time_has_elapsed_switch_to_next_step_if") + ":"); + + if (GUILayout.Toggle(_stepMetric == StepChangeMetric.Default, GetStepChangeMetricDescription(StepChangeMetric.Default))) { + _stepMetric = StepChangeMetric.Default; + } + + if (GUILayout.Toggle(_stepMetric == StepChangeMetric.FirstFlow, GetStepChangeMetricDescription(StepChangeMetric.FirstFlow))) { + _stepMetric = StepChangeMetric.FirstFlow; + } + + if (GUILayout.Toggle(_stepMetric == StepChangeMetric.FirstWait, GetStepChangeMetricDescription(StepChangeMetric.FirstWait))) { + _stepMetric = StepChangeMetric.FirstWait; + } + + if (GUILayout.Toggle(_stepMetric == StepChangeMetric.NoFlow, GetStepChangeMetricDescription(StepChangeMetric.NoFlow))) { + _stepMetric = StepChangeMetric.NoFlow; + } + + if (GUILayout.Toggle(_stepMetric == StepChangeMetric.NoWait, GetStepChangeMetricDescription(StepChangeMetric.NoWait))) { + _stepMetric = StepChangeMetric.NoWait; + } + } else { + GUILayout.Label(Translation.GetString("Adaptive_step_switching") + ": " + GetStepChangeMetricDescription(_stepMetric)); + } + + GUILayout.EndVertical(); + } + + private void BuildFlowPolicyDisplay(bool editable) { + string formatStr; + if (_waitFlowBalance < 0.01f) + formatStr = "{0:0.###}"; + else if (_waitFlowBalance < 0.1f) + formatStr = "{0:0.##}"; + else + formatStr = "{0:0.#}"; + + GUILayout.BeginHorizontal(); + if (editable) { + GUILayout.Label(Translation.GetString("Sensitivity") + " (" + String.Format(formatStr, _waitFlowBalance) + ", " + getWaitFlowBalanceInfo() + "):"); + if (_waitFlowBalance <= 0.01f) { + if (_waitFlowBalance >= 0) { + if (GUILayout.Button("-.001")) { + _waitFlowBalance -= 0.001f; + } + } + if (_waitFlowBalance < 0.01f) { + if (GUILayout.Button("+.001")) { + _waitFlowBalance += 0.001f; + } + } + } else if (_waitFlowBalance <= 0.1f) { + if (GUILayout.Button("-.01")) { + _waitFlowBalance -= 0.01f; + } + if (_waitFlowBalance < 0.1f) { + if (GUILayout.Button("+.01")) { + _waitFlowBalance += 0.01f; + } + } + } + if (_waitFlowBalance < 0) + _waitFlowBalance = 0; + if (_waitFlowBalance > 10) + _waitFlowBalance = 10; + GUILayout.EndHorizontal(); + + _waitFlowBalance = GUILayout.HorizontalSlider(_waitFlowBalance, 0.001f, 10f); + + // step snapping + if (_waitFlowBalance < 0.001f) { + _waitFlowBalance = 0.001f; + } else if (_waitFlowBalance < 0.01f) { + _waitFlowBalance = Mathf.Round(_waitFlowBalance * 1000f) * 0.001f; + } else if (_waitFlowBalance < 0.1f) { + _waitFlowBalance = Mathf.Round(_waitFlowBalance * 100f) * 0.01f; + } else if (_waitFlowBalance < 10f) { + _waitFlowBalance = Mathf.Round(_waitFlowBalance * 10f) * 0.1f; + } else { + _waitFlowBalance = 10f; + } + + GUILayout.BeginHorizontal(); + GUIStyle style = new GUIStyle(); + style.normal.textColor = Color.white; + style.alignment = TextAnchor.LowerLeft; + GUILayout.Label(Translation.GetString("Low"), style, new GUILayoutOption[] { GUILayout.Height(10) }); + style.alignment = TextAnchor.LowerRight; + GUILayout.Label(Translation.GetString("High"), style, new GUILayoutOption[] { GUILayout.Height(10) }); + } else { + GUILayout.Label(Translation.GetString("Sensitivity") + ": " + String.Format(formatStr, _waitFlowBalance) + " (" + getWaitFlowBalanceInfo() + ")"); + } + GUILayout.EndHorizontal(); + + GUILayout.Space(5); + } + + private string GetStepChangeMetricDescription(StepChangeMetric metric) { + switch (metric) { + case StepChangeMetric.Default: + default: + return Translation.GetString("flow_ratio") + " < " + Translation.GetString("wait_ratio") + " (" + Translation.GetString("default") + ")"; + case StepChangeMetric.FirstFlow: + return Translation.GetString("flow_ratio") + " > 0"; + case StepChangeMetric.FirstWait: + return Translation.GetString("wait_ratio") + " > 0"; + case StepChangeMetric.NoFlow: + return Translation.GetString("flow_ratio") + " = 0"; + case StepChangeMetric.NoWait: + return Translation.GetString("wait_ratio") + " = 0"; + } + } + + private void _guiTimedTrafficLightsNode() { + _cursorInSecondaryPanel = false; + + _windowRect2 = GUILayout.Window(252, _windowRect2, _guiTimedTrafficLightsNodeWindow, Translation.GetString("Select_nodes_windowTitle"), WindowStyle); _cursorInSecondaryPanel = _windowRect2.Contains(Event.current.mousePosition); } - private void _guiTimedTrafficLights() { - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; - TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; + private void _guiTimedTrafficLights() { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; + TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; - _cursorInSecondaryPanel = false; + _cursorInSecondaryPanel = false; - _windowRect = GUILayout.Window(253, _windowRect, _guiTimedControlPanel, Translation.GetString("Timed_traffic_lights_manager"), WindowStyle); + _windowRect = GUILayout.Window(253, _windowRect, _guiTimedControlPanel, Translation.GetString("Timed_traffic_lights_manager"), WindowStyle); - _cursorInSecondaryPanel = _windowRect.Contains(Event.current.mousePosition); + _cursorInSecondaryPanel = _windowRect.Contains(Event.current.mousePosition); - GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(1, 1, 1)); // revert scaling + GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(1, 1, 1)); // revert scaling ShowGUI(); } - private void _guiTimedTrafficLightsCopy() { - _cursorInSecondaryPanel = false; - - _windowRect2 = GUILayout.Window(255, _windowRect2, _guiTimedTrafficLightsPasteWindow, Translation.GetString("Paste"), WindowStyle); - - _cursorInSecondaryPanel = _windowRect2.Contains(Event.current.mousePosition); - } - - private void _guiTimedTrafficLightsPasteWindow(int num) { - GUILayout.Label(Translation.GetString("Select_junction")); - } - - private void _guiTimedTrafficLightsNodeWindow(int num) { - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - if (SelectedNodeIds.Count < 1) { - GUILayout.Label(Translation.GetString("Select_nodes")); - } else { - var txt = SelectedNodeIds.Aggregate("", (current, t) => current + (Translation.GetString("Node") + " " + t + "\n")); - - GUILayout.Label(txt); - - if (SelectedNodeIds.Count > 0 && GUILayout.Button(Translation.GetString("Deselect_all_nodes"))) { - ClearSelectedNodes(); - } - if (!GUILayout.Button(Translation.GetString("Setup_timed_traffic_light"))) return; - - _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; - foreach (var nodeId in SelectedNodeIds) { - tlsMan.SetUpTimedTrafficLight(nodeId, SelectedNodeIds); - RefreshCurrentTimedNodeIds(nodeId); - } - - MainTool.SetToolMode(ToolMode.TimedLightsShowLights); - } - - DragWindow(ref _windowRect2); - } - - private string getWaitFlowBalanceInfo() { - if (_waitFlowBalance < 0.1f) { - return Translation.GetString("Extreme_long_green/red_phases"); - } else if (_waitFlowBalance < 0.5f) { - return Translation.GetString("Very_long_green/red_phases"); - } else if (_waitFlowBalance < 0.75f) { - return Translation.GetString("Long_green/red_phases"); - } else if (_waitFlowBalance < 1.25f) { - return Translation.GetString("Moderate_green/red_phases"); - } else if (_waitFlowBalance < 1.5f) { - return Translation.GetString("Short_green/red_phases"); - } else if (_waitFlowBalance < 2.5f) { - return Translation.GetString("Very_short_green/red_phases"); - } else { - return Translation.GetString("Extreme_short_green/red_phases"); - } - } - - private void DisableTimed() { - if (SelectedNodeIds.Count <= 0) return; - - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - foreach (var selectedNodeId in SelectedNodeIds) { - tlsMan.RemoveNodeFromSimulation(selectedNodeId, true, false); - RefreshCurrentTimedNodeIds(selectedNodeId); - } - } - - private void AddSelectedNode(ushort node) { - SelectedNodeIds.Add(node); - } - - private bool IsNodeSelected(ushort node) { - return SelectedNodeIds.Contains(node); - } - - private void RemoveSelectedNode(ushort node) { - SelectedNodeIds.Remove(node); - } - - private void ClearSelectedNodes() { - SelectedNodeIds.Clear(); - } - - private void drawStraightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { - switch (state) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(rect, TextureResources.GreenLightStraightTexture2D); - break; - case RoadBaseAI.TrafficLightState.GreenToRed: - GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(rect, TextureResources.RedLightStraightTexture2D); - break; - case RoadBaseAI.TrafficLightState.RedToGreen: - GUI.DrawTexture(rect, TextureResources.YellowLightStraightTexture2D); - break; - } - } - - private void drawForwardLeftLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { - switch (state) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(rect, TextureResources.GreenLightForwardLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.GreenToRed: - GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(rect, TextureResources.RedLightForwardLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.RedToGreen: - GUI.DrawTexture(rect, TextureResources.YellowLightForwardLeftTexture2D); - break; - } - } - - private void drawForwardRightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { - switch (state) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(rect, TextureResources.GreenLightForwardRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.GreenToRed: - GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(rect, TextureResources.RedLightForwardRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.RedToGreen: - GUI.DrawTexture(rect, TextureResources.YellowLightForwardRightTexture2D); - break; - } - } - - private void drawLeftLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { - switch (state) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(rect, TextureResources.GreenLightLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.GreenToRed: - GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(rect, TextureResources.RedLightLeftTexture2D); - break; - case RoadBaseAI.TrafficLightState.RedToGreen: - GUI.DrawTexture(rect, TextureResources.YellowLightLeftTexture2D); - break; - } - } - - private void drawRightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { - switch (state) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(rect, TextureResources.GreenLightRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.GreenToRed: - GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(rect, TextureResources.RedLightRightTexture2D); - break; - case RoadBaseAI.TrafficLightState.RedToGreen: - GUI.DrawTexture(rect, TextureResources.YellowLightRightTexture2D); - break; - } - } - - private void drawMainLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { - switch (state) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(rect, TextureResources.GreenLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.GreenToRed: - GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(rect, TextureResources.RedLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.RedToGreen: - GUI.DrawTexture(rect, TextureResources.YellowRedLightTexture2D); - break; - } - } - - public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { - if (! ToolMode.TimedLightsShowLights.Equals(toolMode) && - ! ToolMode.TimedLightsSelectNode.Equals(toolMode) && - ! ToolMode.TimedLightsAddNode.Equals(toolMode) && - ! ToolMode.TimedLightsRemoveNode.Equals(toolMode) && - ! ToolMode.TimedLightsCopyLights.Equals(toolMode)) { - // TODO refactor timed light related tool modes to sub tool modes - return; - } - if (viewOnly && !Options.timedLightsOverlay) - return; - - Vector3 camPos = Singleton.instance.m_simulationView.m_position; - - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - - foreach (ushort nodeId in currentTimedNodeIds) { - if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { - continue; - } - - if (SelectedNodeIds.Contains((ushort)nodeId)) { - continue; - } - - if (tlsMan.HasTimedSimulation((ushort)nodeId)) { - ITimedTrafficLights timedNode = tlsMan.TrafficLightSimulations[nodeId].timedLight; - - var nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; - - Texture2D tex = timedNode.IsStarted() ? (timedNode.IsInTestMode() ? TextureResources.ClockTestTexture2D : TextureResources.ClockPlayTexture2D) : TextureResources.ClockPauseTexture2D; - MainTool.DrawGenericSquareOverlayTexture(tex, camPos, nodePos, 120f, false); - } - } - } - - private void ShowGUI() { - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; - JunctionRestrictionsManager junctionRestrictionsManager = JunctionRestrictionsManager.Instance; - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - - var hoveredSegment = false; - - foreach (var nodeId in SelectedNodeIds) { - if (!tlsMan.HasTimedSimulation(nodeId)) { - continue; - } - - ITimedTrafficLights timedNode = tlsMan.TrafficLightSimulations[nodeId].timedLight; - - var nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; - - Vector3 nodeScreenPos; - bool nodeVisible = MainTool.WorldToScreenPoint(nodePos, out nodeScreenPos); - - if (!nodeVisible) - continue; - - var diff = nodePos - Camera.main.transform.position; - var zoom = 1.0f / diff.magnitude * 100f * MainTool.GetBaseZoom(); - - for (int i = 0; i < 8; ++i) { - ushort srcSegmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - srcSegmentId = node.GetSegment(i); - return true; - }); - - if (srcSegmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(srcSegmentId, nodeId); - - ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(srcSegmentId, startNode, false); - if (liveSegmentLights == null) - continue; - - bool showPedLight = liveSegmentLights.PedestrianLightState != null && junctionRestrictionsManager.IsPedestrianCrossingAllowed(liveSegmentLights.SegmentId, liveSegmentLights.StartNode); - - var timedActive = timedNode.IsStarted(); - if (! timedActive) { - liveSegmentLights.MakeRedOrGreen(); - } - - var offset = 17f; - Vector3 segmentLightPos = nodePos; - - if (Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startNode == nodeId) { - segmentLightPos.x += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.x * offset; - segmentLightPos.y += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.y; - segmentLightPos.z += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.z * offset; - } else { - segmentLightPos.x += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.x * offset; - segmentLightPos.y += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.y; - segmentLightPos.z += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.z * offset; - } - - Vector3 screenPos; - bool segmentLightVisible = MainTool.WorldToScreenPoint(segmentLightPos, out screenPos); - - if (!segmentLightVisible) - continue; - - var guiColor = GUI.color; - - var manualPedestrianWidth = 36f * zoom; - var manualPedestrianHeight = 35f * zoom; - - var pedestrianWidth = 36f * zoom; - var pedestrianHeight = 61f * zoom; - - // original / 2.5 - var lightWidth = 41f * zoom; - var lightHeight = 97f * zoom; - - // SWITCH MODE BUTTON - var modeWidth = 41f * zoom; - var modeHeight = 38f * zoom; - - if (showPedLight) { - // pedestrian light - - // SWITCH MANUAL PEDESTRIAN LIGHT BUTTON - if (!timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && - (_hoveredButton[1] == 1 || _hoveredButton[1] == 2) && - _hoveredNode == nodeId); - GUI.color = guiColor; - - var myRect2 = new Rect(screenPos.x - manualPedestrianWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) + 5f * zoom, - screenPos.y - manualPedestrianHeight / 2 - 9f * zoom, manualPedestrianWidth, - manualPedestrianHeight); - - GUI.DrawTexture(myRect2, liveSegmentLights.ManualPedestrianMode ? TextureResources.PedestrianModeManualTexture2D : TextureResources.PedestrianModeAutomaticTexture2D); - - if (myRect2.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 1; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked()) { - liveSegmentLights.ManualPedestrianMode = !liveSegmentLights.ManualPedestrianMode; - } - } - } - - // SWITCH PEDESTRIAN LIGHT - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 2 && _hoveredNode == nodeId); - - GUI.color = guiColor; - - var myRect3 = new Rect(screenPos.x - pedestrianWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) + 5f * zoom, screenPos.y - pedestrianHeight / 2 + 22f * zoom, pedestrianWidth, pedestrianHeight); - - switch (liveSegmentLights.PedestrianLightState) { - case RoadBaseAI.TrafficLightState.Green: - GUI.DrawTexture(myRect3, TextureResources.PedestrianGreenLightTexture2D); - break; - case RoadBaseAI.TrafficLightState.Red: - default: - GUI.DrawTexture(myRect3, TextureResources.PedestrianRedLightTexture2D); - break; - } - - if (myRect3.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 2; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - if (!liveSegmentLights.ManualPedestrianMode) { - liveSegmentLights.ManualPedestrianMode = true; - } else { - liveSegmentLights.ChangeLightPedestrian(); - } - } - } - } - - int lightOffset = -1; - foreach (ExtVehicleType vehicleType in liveSegmentLights.VehicleTypes) { - HashSet laneIndices = new HashSet(); - for (byte laneIndex = 0; laneIndex < liveSegmentLights.VehicleTypeByLaneIndex.Length; ++laneIndex) { - if (liveSegmentLights.VehicleTypeByLaneIndex[laneIndex] == vehicleType) { - laneIndices.Add(laneIndex); - } - } - //Log._Debug($"Traffic light @ seg. {srcSegmentId} node {nodeId}. Lane indices for vehicleType {vehicleType}: {string.Join(",", laneIndices.Select(x => x.ToString()).ToArray())}"); - - ++lightOffset; - ICustomSegmentLight liveSegmentLight = liveSegmentLights.GetCustomLight(vehicleType); - - Vector3 offsetScreenPos = screenPos; - offsetScreenPos.y -= (lightHeight + 10f * zoom) * lightOffset; - - if (!timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == -1 && - _hoveredNode == nodeId); - GUI.color = guiColor; - - var myRect1 = new Rect(offsetScreenPos.x - modeWidth / 2, - offsetScreenPos.y - modeHeight / 2 + modeHeight - 7f * zoom, modeWidth, modeHeight); - - GUI.DrawTexture(myRect1, TextureResources.LightModeTexture2D); - - if (myRect1.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = -1; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked()) { - liveSegmentLight.ToggleMode(); - timedNode.ChangeLightMode(srcSegmentId, vehicleType, liveSegmentLight.CurrentMode); - } - } - } - - if (vehicleType != ExtVehicleType.None) { - // Info sign - var infoWidth = 56.125f * zoom; - var infoHeight = 51.375f * zoom; - - int numInfos = 0; - for (int k = 0; k < TrafficManagerTool.InfoSignsToDisplay.Length; ++k) { - if ((TrafficManagerTool.InfoSignsToDisplay[k] & vehicleType) == ExtVehicleType.None) - continue; - var infoRect = new Rect(offsetScreenPos.x + modeWidth / 2f + 7f * zoom * (float)(numInfos + 1) + infoWidth * (float)numInfos, offsetScreenPos.y - infoHeight / 2f, infoWidth, infoHeight); - guiColor.a = MainTool.GetHandleAlpha(false); - GUI.DrawTexture(infoRect, TextureResources.VehicleInfoSignTextures[TrafficManagerTool.InfoSignsToDisplay[k]]); - ++numInfos; - } - } + private void _guiTimedTrafficLightsCopy() { + _cursorInSecondaryPanel = false; + + _windowRect2 = GUILayout.Window(255, _windowRect2, _guiTimedTrafficLightsPasteWindow, Translation.GetString("Paste"), WindowStyle); + + _cursorInSecondaryPanel = _windowRect2.Contains(Event.current.mousePosition); + } + + private void _guiTimedTrafficLightsPasteWindow(int num) { + GUILayout.Label(Translation.GetString("Select_junction")); + } + + private void _guiTimedTrafficLightsNodeWindow(int num) { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + if (SelectedNodeIds.Count < 1) { + GUILayout.Label(Translation.GetString("Select_nodes")); + } else { + var txt = SelectedNodeIds.Aggregate("", (current, t) => current + (Translation.GetString("Node") + " " + t + "\n")); + + GUILayout.Label(txt); + + if (SelectedNodeIds.Count > 0 && GUILayout.Button(Translation.GetString("Deselect_all_nodes"))) { + ClearSelectedNodes(); + } + if (!GUILayout.Button(Translation.GetString("Setup_timed_traffic_light"))) return; + + _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; + foreach (var nodeId in SelectedNodeIds) { + tlsMan.SetUpTimedTrafficLight(nodeId, SelectedNodeIds); + RefreshCurrentTimedNodeIds(nodeId); + } + + MainTool.SetToolMode(ToolMode.TimedLightsShowLights); + } + + DragWindow(ref _windowRect2); + } + + private string getWaitFlowBalanceInfo() { + if (_waitFlowBalance < 0.1f) { + return Translation.GetString("Extreme_long_green/red_phases"); + } else if (_waitFlowBalance < 0.5f) { + return Translation.GetString("Very_long_green/red_phases"); + } else if (_waitFlowBalance < 0.75f) { + return Translation.GetString("Long_green/red_phases"); + } else if (_waitFlowBalance < 1.25f) { + return Translation.GetString("Moderate_green/red_phases"); + } else if (_waitFlowBalance < 1.5f) { + return Translation.GetString("Short_green/red_phases"); + } else if (_waitFlowBalance < 2.5f) { + return Translation.GetString("Very_short_green/red_phases"); + } else { + return Translation.GetString("Extreme_short_green/red_phases"); + } + } + + private void DisableTimed() { + if (SelectedNodeIds.Count <= 0) return; + + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + foreach (var selectedNodeId in SelectedNodeIds) { + tlsMan.RemoveNodeFromSimulation(selectedNodeId, true, false); + RefreshCurrentTimedNodeIds(selectedNodeId); + } + } + + private void AddSelectedNode(ushort node) { + SelectedNodeIds.Add(node); + } + + private bool IsNodeSelected(ushort node) { + return SelectedNodeIds.Contains(node); + } + + private void RemoveSelectedNode(ushort node) { + SelectedNodeIds.Remove(node); + } + + private void ClearSelectedNodes() { + SelectedNodeIds.Clear(); + } + + private void drawStraightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { + switch (state) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(rect, TextureResources.GreenLightStraightTexture2D); + break; + case RoadBaseAI.TrafficLightState.GreenToRed: + GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(rect, TextureResources.RedLightStraightTexture2D); + break; + case RoadBaseAI.TrafficLightState.RedToGreen: + GUI.DrawTexture(rect, TextureResources.YellowLightStraightTexture2D); + break; + } + } + + private void drawForwardLeftLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { + switch (state) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(rect, TextureResources.GreenLightForwardLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.GreenToRed: + GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(rect, TextureResources.RedLightForwardLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.RedToGreen: + GUI.DrawTexture(rect, TextureResources.YellowLightForwardLeftTexture2D); + break; + } + } + + private void drawForwardRightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { + switch (state) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(rect, TextureResources.GreenLightForwardRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.GreenToRed: + GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(rect, TextureResources.RedLightForwardRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.RedToGreen: + GUI.DrawTexture(rect, TextureResources.YellowLightForwardRightTexture2D); + break; + } + } + + private void drawLeftLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { + switch (state) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(rect, TextureResources.GreenLightLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.GreenToRed: + GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(rect, TextureResources.RedLightLeftTexture2D); + break; + case RoadBaseAI.TrafficLightState.RedToGreen: + GUI.DrawTexture(rect, TextureResources.YellowLightLeftTexture2D); + break; + } + } + + private void drawRightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { + switch (state) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(rect, TextureResources.GreenLightRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.GreenToRed: + GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(rect, TextureResources.RedLightRightTexture2D); + break; + case RoadBaseAI.TrafficLightState.RedToGreen: + GUI.DrawTexture(rect, TextureResources.YellowLightRightTexture2D); + break; + } + } + + private void drawMainLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { + switch (state) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(rect, TextureResources.GreenLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.GreenToRed: + GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(rect, TextureResources.RedLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.RedToGreen: + GUI.DrawTexture(rect, TextureResources.YellowRedLightTexture2D); + break; + } + } + + public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { + if (! ToolMode.TimedLightsShowLights.Equals(toolMode) && + ! ToolMode.TimedLightsSelectNode.Equals(toolMode) && + ! ToolMode.TimedLightsAddNode.Equals(toolMode) && + ! ToolMode.TimedLightsRemoveNode.Equals(toolMode) && + ! ToolMode.TimedLightsCopyLights.Equals(toolMode)) { + // TODO refactor timed light related tool modes to sub tool modes + return; + } + if (viewOnly && !Options.timedLightsOverlay) + return; + + Vector3 camPos = Singleton.instance.m_simulationView.m_position; + + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + + foreach (ushort nodeId in currentTimedNodeIds) { + if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { + continue; + } + + if (SelectedNodeIds.Contains((ushort)nodeId)) { + continue; + } + + if (tlsMan.HasTimedSimulation((ushort)nodeId)) { + ITimedTrafficLights timedNode = tlsMan.TrafficLightSimulations[nodeId].timedLight; + + var nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; + + Texture2D tex = timedNode.IsStarted() ? (timedNode.IsInTestMode() ? TextureResources.ClockTestTexture2D : TextureResources.ClockPlayTexture2D) : TextureResources.ClockPauseTexture2D; + MainTool.DrawGenericSquareOverlayTexture(tex, camPos, nodePos, 120f, false); + } + } + } + + private void ShowGUI() { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; + JunctionRestrictionsManager junctionRestrictionsManager = JunctionRestrictionsManager.Instance; + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + + var hoveredSegment = false; + + foreach (var nodeId in SelectedNodeIds) { + if (!tlsMan.HasTimedSimulation(nodeId)) { + continue; + } + + ITimedTrafficLights timedNode = tlsMan.TrafficLightSimulations[nodeId].timedLight; + + var nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; + + Vector3 nodeScreenPos; + bool nodeVisible = MainTool.WorldToScreenPoint(nodePos, out nodeScreenPos); + + if (!nodeVisible) + continue; + + var diff = nodePos - Camera.main.transform.position; + var zoom = 1.0f / diff.magnitude * 100f * MainTool.GetBaseZoom(); + + for (int i = 0; i < 8; ++i) { + ushort srcSegmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + srcSegmentId = node.GetSegment(i); + return true; + }); + + if (srcSegmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(srcSegmentId, nodeId); + + ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(srcSegmentId, startNode, false); + if (liveSegmentLights == null) + continue; + + bool showPedLight = liveSegmentLights.PedestrianLightState != null && junctionRestrictionsManager.IsPedestrianCrossingAllowed(liveSegmentLights.SegmentId, liveSegmentLights.StartNode); + + var timedActive = timedNode.IsStarted(); + if (! timedActive) { + liveSegmentLights.MakeRedOrGreen(); + } + + var offset = 17f; + Vector3 segmentLightPos = nodePos; + + if (Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startNode == nodeId) { + segmentLightPos.x += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.x * offset; + segmentLightPos.y += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.y; + segmentLightPos.z += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.z * offset; + } else { + segmentLightPos.x += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.x * offset; + segmentLightPos.y += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.y; + segmentLightPos.z += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.z * offset; + } + + Vector3 screenPos; + bool segmentLightVisible = MainTool.WorldToScreenPoint(segmentLightPos, out screenPos); + + if (!segmentLightVisible) + continue; + + var guiColor = GUI.color; + + var manualPedestrianWidth = 36f * zoom; + var manualPedestrianHeight = 35f * zoom; + + var pedestrianWidth = 36f * zoom; + var pedestrianHeight = 61f * zoom; + + // original / 2.5 + var lightWidth = 41f * zoom; + var lightHeight = 97f * zoom; + + // SWITCH MODE BUTTON + var modeWidth = 41f * zoom; + var modeHeight = 38f * zoom; + + if (showPedLight) { + // pedestrian light + + // SWITCH MANUAL PEDESTRIAN LIGHT BUTTON + if (!timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && + (_hoveredButton[1] == 1 || _hoveredButton[1] == 2) && + _hoveredNode == nodeId); + GUI.color = guiColor; + + var myRect2 = new Rect(screenPos.x - manualPedestrianWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) + 5f * zoom, + screenPos.y - manualPedestrianHeight / 2 - 9f * zoom, manualPedestrianWidth, + manualPedestrianHeight); + + GUI.DrawTexture(myRect2, liveSegmentLights.ManualPedestrianMode ? TextureResources.PedestrianModeManualTexture2D : TextureResources.PedestrianModeAutomaticTexture2D); + + if (myRect2.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 1; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked()) { + liveSegmentLights.ManualPedestrianMode = !liveSegmentLights.ManualPedestrianMode; + } + } + } + + // SWITCH PEDESTRIAN LIGHT + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 2 && _hoveredNode == nodeId); + + GUI.color = guiColor; + + var myRect3 = new Rect(screenPos.x - pedestrianWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) + 5f * zoom, screenPos.y - pedestrianHeight / 2 + 22f * zoom, pedestrianWidth, pedestrianHeight); + + switch (liveSegmentLights.PedestrianLightState) { + case RoadBaseAI.TrafficLightState.Green: + GUI.DrawTexture(myRect3, TextureResources.PedestrianGreenLightTexture2D); + break; + case RoadBaseAI.TrafficLightState.Red: + default: + GUI.DrawTexture(myRect3, TextureResources.PedestrianRedLightTexture2D); + break; + } + + if (myRect3.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 2; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + if (!liveSegmentLights.ManualPedestrianMode) { + liveSegmentLights.ManualPedestrianMode = true; + } else { + liveSegmentLights.ChangeLightPedestrian(); + } + } + } + } + + int lightOffset = -1; + foreach (ExtVehicleType vehicleType in liveSegmentLights.VehicleTypes) { + HashSet laneIndices = new HashSet(); + for (byte laneIndex = 0; laneIndex < liveSegmentLights.VehicleTypeByLaneIndex.Length; ++laneIndex) { + if (liveSegmentLights.VehicleTypeByLaneIndex[laneIndex] == vehicleType) { + laneIndices.Add(laneIndex); + } + } + //Log._Debug($"Traffic light @ seg. {srcSegmentId} node {nodeId}. Lane indices for vehicleType {vehicleType}: {string.Join(",", laneIndices.Select(x => x.ToString()).ToArray())}"); + + ++lightOffset; + ICustomSegmentLight liveSegmentLight = liveSegmentLights.GetCustomLight(vehicleType); + + Vector3 offsetScreenPos = screenPos; + offsetScreenPos.y -= (lightHeight + 10f * zoom) * lightOffset; + + if (!timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == -1 && + _hoveredNode == nodeId); + GUI.color = guiColor; + + var myRect1 = new Rect(offsetScreenPos.x - modeWidth / 2, + offsetScreenPos.y - modeHeight / 2 + modeHeight - 7f * zoom, modeWidth, modeHeight); + + GUI.DrawTexture(myRect1, TextureResources.LightModeTexture2D); + + if (myRect1.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = -1; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked()) { + liveSegmentLight.ToggleMode(); + timedNode.ChangeLightMode(srcSegmentId, vehicleType, liveSegmentLight.CurrentMode); + } + } + } + + if (vehicleType != ExtVehicleType.None) { + // Info sign + var infoWidth = 56.125f * zoom; + var infoHeight = 51.375f * zoom; + + int numInfos = 0; + for (int k = 0; k < TrafficManagerTool.InfoSignsToDisplay.Length; ++k) { + if ((TrafficManagerTool.InfoSignsToDisplay[k] & vehicleType) == ExtVehicleType.None) + continue; + var infoRect = new Rect(offsetScreenPos.x + modeWidth / 2f + 7f * zoom * (float)(numInfos + 1) + infoWidth * (float)numInfos, offsetScreenPos.y - infoHeight / 2f, infoWidth, infoHeight); + guiColor.a = MainTool.GetHandleAlpha(false); + GUI.DrawTexture(infoRect, TextureResources.VehicleInfoSignTextures[TrafficManagerTool.InfoSignsToDisplay[k]]); + ++numInfos; + } + } #if DEBUG - if (timedActive /*&& _timedShowNumbers*/) { - //var prioSeg = TrafficPriorityManager.Instance.GetPrioritySegment(nodeId, srcSegmentId); - - var counterSize = 20f * zoom; - var yOffset = counterSize + 77f * zoom - modeHeight * 2; - //var carNumRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset, counterSize, counterSize); - var segIdRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset - counterSize - 2f, counterSize, counterSize); - - _counterStyle.fontSize = (int)(15f * zoom); - _counterStyle.normal.textColor = new Color(1f, 0f, 0f); - - /*String labelStr = "n/a"; - if (prioSeg != null) { - labelStr = prioSeg.GetRegisteredVehicleCount(laneIndices).ToString() + " " + Translation.GetString("incoming"); - } - GUI.Label(carNumRect, labelStr, _counterStyle);*/ - - _counterStyle.normal.textColor = new Color(1f, 0f, 0f); - GUI.Label(segIdRect, Translation.GetString("Segment") + " " + srcSegmentId, _counterStyle); - } + if (timedActive /*&& _timedShowNumbers*/) { + //var prioSeg = TrafficPriorityManager.Instance.GetPrioritySegment(nodeId, srcSegmentId); + + var counterSize = 20f * zoom; + var yOffset = counterSize + 77f * zoom - modeHeight * 2; + //var carNumRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset, counterSize, counterSize); + var segIdRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset - counterSize - 2f, counterSize, counterSize); + + _counterStyle.fontSize = (int)(15f * zoom); + _counterStyle.normal.textColor = new Color(1f, 0f, 0f); + + /*String labelStr = "n/a"; + if (prioSeg != null) { + labelStr = prioSeg.GetRegisteredVehicleCount(laneIndices).ToString() + " " + Translation.GetString("incoming"); + } + GUI.Label(carNumRect, labelStr, _counterStyle);*/ + + _counterStyle.normal.textColor = new Color(1f, 0f, 0f); + GUI.Label(segIdRect, Translation.GetString("Segment") + " " + srcSegmentId, _counterStyle); + } #endif - if (lightOffset == 0 && showPedLight) { - // PEDESTRIAN COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; + if (lightOffset == 0 && showPedLight) { + // PEDESTRIAN COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 3); + + float numOffset; + + if (liveSegmentLights.PedestrianLightState == RoadBaseAI.TrafficLightState.Red) { // TODO check this + numOffset = counterSize + 53f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 29f * zoom - modeHeight * 2; + } + + var myRectCounterNum = + new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 1f) + 24f * zoom - pedestrianWidth / 2, + offsetScreenPos.y - numOffset, counterSize, counterSize); + + _counterStyle.fontSize = (int)(15f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 2; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + } + + ExtSegment seg = segMan.ExtSegments[srcSegmentId]; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(srcSegmentId, startNode)]; + + if (seg.oneWay && segEnd.outgoing) + continue; + + bool hasOutgoingLeftSegment; + bool hasOutgoingForwardSegment; + bool hasOutgoingRightSegment; + segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[nodeId], out hasOutgoingLeftSegment, out hasOutgoingForwardSegment, out hasOutgoingRightSegment); + + bool hasOtherLight = false; + switch (liveSegmentLight.CurrentMode) { + case LightMode.Simple: { + // no arrow light + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); + + GUI.color = guiColor; + + var myRect4 = + new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + + drawMainLightTexture(liveSegmentLight.LightMain, myRect4); + + if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeMainLight(); + } + } + + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 0); + + float numOffset; + + if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } + + var myRectCounterNum = + new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - numOffset, counterSize, counterSize); + + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + + GUI.color = guiColor; + } + break; + case LightMode.SingleLeft: + if (hasOutgoingLeftSegment) { + // left arrow light + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); + + GUI.color = guiColor; + + var myRect4 = + new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + + drawLeftLightTexture(liveSegmentLight.LightLeft, myRect4); + + if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeLeftLight(); + } + } + + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 1); + + float numOffset; + + if (liveSegmentLight.LightLeft == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } + + var myRectCounterNum = + new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth), + offsetScreenPos.y - numOffset, counterSize, counterSize); + + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + } + + // forward-right arrow light + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && _hoveredNode == nodeId); + + GUI.color = guiColor; + + var myRect5 = + new Rect(offsetScreenPos.x - lightWidth / 2 - pedestrianWidth - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + + if (hasOutgoingForwardSegment && hasOutgoingRightSegment) { + drawForwardRightLightTexture(liveSegmentLight.LightMain, myRect5); + hasOtherLight = true; + } else if (hasOutgoingForwardSegment) { + drawStraightLightTexture(liveSegmentLight.LightMain, myRect5); + hasOtherLight = true; + } else if (hasOutgoingRightSegment) { + drawRightLightTexture(liveSegmentLight.LightMain, myRect5); + hasOtherLight = true; + } + + if (hasOtherLight && myRect5.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 4; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeMainLight(); + } + } + + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 0); + + float numOffset; + if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } + + var myRectCounterNum = + new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), + offsetScreenPos.y - numOffset, counterSize, counterSize); + + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 4; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + break; + case LightMode.SingleRight: { + // forward-left light + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); + + GUI.color = guiColor; + + var myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + + var lightType = 0; + + hasOtherLight = false; + if (hasOutgoingForwardSegment && hasOutgoingLeftSegment) { + hasOtherLight = true; + drawForwardLeftLightTexture(liveSegmentLight.LightMain, myRect4); + lightType = 1; + } else if (hasOutgoingForwardSegment) { + hasOtherLight = true; + if (!hasOutgoingRightSegment) { + myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + } + + drawStraightLightTexture(liveSegmentLight.LightMain, myRect4); + } else if (hasOutgoingLeftSegment) { + hasOtherLight = true; + if (!hasOutgoingRightSegment) { + myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + } + + drawLeftLightTexture(liveSegmentLight.LightMain, myRect4); + } + + + if (hasOtherLight && myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeMainLight(); + } + } + + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, lightType); + + float numOffset; + + if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 3); - - float numOffset; + var myRectCounterNum = + new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? (hasOutgoingRightSegment ? lightWidth * 2 : lightWidth) : (hasOutgoingRightSegment ? lightWidth : 0f)), + offsetScreenPos.y - numOffset, counterSize, counterSize); - if (liveSegmentLights.PedestrianLightState == RoadBaseAI.TrafficLightState.Red) { // TODO check this - numOffset = counterSize + 53f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 29f * zoom - modeHeight * 2; - } + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - var myRectCounterNum = - new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 1f) + 24f * zoom - pedestrianWidth / 2, - offsetScreenPos.y - numOffset, counterSize, counterSize); + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - _counterStyle.fontSize = (int)(15f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + // right arrow light + if (hasOutgoingRightSegment) { + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && + _hoveredNode == nodeId); - if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 2; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - } + GUI.color = guiColor; - ExtSegment seg = segMan.ExtSegments[srcSegmentId]; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(srcSegmentId, startNode)]; + var rect5 = + new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - if (seg.oneWay && segEnd.outgoing) - continue; + drawRightLightTexture(liveSegmentLight.LightRight, rect5); - bool hasOutgoingLeftSegment; - bool hasOutgoingForwardSegment; - bool hasOutgoingRightSegment; - segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[nodeId], out hasOutgoingLeftSegment, out hasOutgoingForwardSegment, out hasOutgoingRightSegment); + if (rect5.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 4; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && + (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeRightLight(); + } + } - bool hasOtherLight = false; - switch (liveSegmentLight.CurrentMode) { - case LightMode.Simple: { - // no arrow light - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; - GUI.color = guiColor; + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 2); - var myRect4 = - new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + float numOffset; - drawMainLightTexture(liveSegmentLight.LightMain, myRect4); + if (liveSegmentLight.LightRight == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } - if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; + var myRectCounterNum = + new Rect( + offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - + pedestrianWidth + 5f * zoom - + (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), + offsetScreenPos.y - numOffset, counterSize, counterSize); - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeMainLight(); - } - } + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 0); - - float numOffset; - - if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - - GUI.color = guiColor; - } - break; - case LightMode.SingleLeft: - if (hasOutgoingLeftSegment) { - // left arrow light - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); - - GUI.color = guiColor; - - var myRect4 = - new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - - drawLeftLightTexture(liveSegmentLight.LightLeft, myRect4); - - if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeLeftLight(); - } - } - - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; - - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 1); - - float numOffset; - - if (liveSegmentLight.LightLeft == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth), - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - } - - // forward-right arrow light - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && _hoveredNode == nodeId); - - GUI.color = guiColor; - - var myRect5 = - new Rect(offsetScreenPos.x - lightWidth / 2 - pedestrianWidth - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - - if (hasOutgoingForwardSegment && hasOutgoingRightSegment) { - drawForwardRightLightTexture(liveSegmentLight.LightMain, myRect5); - hasOtherLight = true; - } else if (hasOutgoingForwardSegment) { - drawStraightLightTexture(liveSegmentLight.LightMain, myRect5); - hasOtherLight = true; - } else if (hasOutgoingRightSegment) { - drawRightLightTexture(liveSegmentLight.LightMain, myRect5); - hasOtherLight = true; - } - - if (hasOtherLight && myRect5.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 4; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeMainLight(); - } - } - - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 0); - - float numOffset; - if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 4; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - break; - case LightMode.SingleRight: { - // forward-left light - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); - - GUI.color = guiColor; - - var myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - - var lightType = 0; - - hasOtherLight = false; - if (hasOutgoingForwardSegment && hasOutgoingLeftSegment) { - hasOtherLight = true; - drawForwardLeftLightTexture(liveSegmentLight.LightMain, myRect4); - lightType = 1; - } else if (hasOutgoingForwardSegment) { - hasOtherLight = true; - if (!hasOutgoingRightSegment) { - myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - } - - drawStraightLightTexture(liveSegmentLight.LightMain, myRect4); - } else if (hasOutgoingLeftSegment) { - hasOtherLight = true; - if (!hasOutgoingRightSegment) { - myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - } - - drawLeftLightTexture(liveSegmentLight.LightMain, myRect4); - } - - - if (hasOtherLight && myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeMainLight(); - } - } - - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; - - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, lightType); - - float numOffset; - - if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? (hasOutgoingRightSegment ? lightWidth * 2 : lightWidth) : (hasOutgoingRightSegment ? lightWidth : 0f)), - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - - // right arrow light - if (hasOutgoingRightSegment) { - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && - _hoveredNode == nodeId); - - GUI.color = guiColor; - - var rect5 = - new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - - drawRightLightTexture(liveSegmentLight.LightRight, rect5); - - if (rect5.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 4; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && - (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeRightLight(); - } - } - - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; - - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 2); - - float numOffset; - - if (liveSegmentLight.LightRight == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect( - offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - - pedestrianWidth + 5f * zoom - - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && - !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 4; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - } - } - break; - default: - // left arrow light - if (hasOutgoingLeftSegment) { - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); + if (myRectCounterNum.Contains(Event.current.mousePosition) && + !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 4; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + } + } + break; + default: + // left arrow light + if (hasOutgoingLeftSegment) { + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); - GUI.color = guiColor; + GUI.color = guiColor; - var offsetLight = lightWidth; - - if (hasOutgoingRightSegment) - offsetLight += lightWidth; - - if (hasOutgoingForwardSegment) - offsetLight += lightWidth; - - var myRect4 = - new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - - drawLeftLightTexture(liveSegmentLight.LightLeft, myRect4); - - if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeLeftLight(); - } - } - - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; - - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 1); - - float numOffset; - - if (liveSegmentLight.LightLeft == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect( - offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - - pedestrianWidth + 5f * zoom - - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth), - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && - !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - } - - // forward arrow light - if (hasOutgoingForwardSegment) { - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && _hoveredNode == nodeId); - - GUI.color = guiColor; - - var offsetLight = lightWidth; - - if (hasOutgoingRightSegment) - offsetLight += lightWidth; - - var myRect6 = - new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - - drawStraightLightTexture(liveSegmentLight.LightMain, myRect6); - - if (myRect6.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 4; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeMainLight(); - } - } - - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; - - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 0); - - float numOffset; - - if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect( - offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - - pedestrianWidth + 5f * zoom - - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth), - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && - !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 4; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - } - - // right arrow light - if (hasOutgoingRightSegment) { - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 5 && _hoveredNode == nodeId); - - GUI.color = guiColor; - - var rect6 = - new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - - drawRightLightTexture(liveSegmentLight.LightRight, rect6); - - if (rect6.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 5; - _hoveredNode = nodeId; - hoveredSegment = true; - - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeRightLight(); - } - } - - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; - - var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 2); - - float numOffset; - - if (liveSegmentLight.LightRight == RoadBaseAI.TrafficLightState.Red) { - numOffset = counterSize + 96f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 40f * zoom - modeHeight * 2; - } - - var myRectCounterNum = - new Rect( - offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - - pedestrianWidth + 5f * zoom - - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), - offsetScreenPos.y - numOffset, counterSize, counterSize); - - _counterStyle.fontSize = (int)(18f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - - if (myRectCounterNum.Contains(Event.current.mousePosition) && - !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 5; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - } - break; - } // end switch liveSegmentLight.CurrentMode - } // end foreach light - } // end foreach segment - } // end foreach node - - if (!hoveredSegment) { - _hoveredButton[0] = 0; - _hoveredButton[1] = 0; - } - } - } -} + var offsetLight = lightWidth; + + if (hasOutgoingRightSegment) + offsetLight += lightWidth; + + if (hasOutgoingForwardSegment) + offsetLight += lightWidth; + + var myRect4 = + new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + + drawLeftLightTexture(liveSegmentLight.LightLeft, myRect4); + + if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeLeftLight(); + } + } + + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 1); + + float numOffset; + + if (liveSegmentLight.LightLeft == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } + + var myRectCounterNum = + new Rect( + offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - + pedestrianWidth + 5f * zoom - + (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth), + offsetScreenPos.y - numOffset, counterSize, counterSize); + + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (myRectCounterNum.Contains(Event.current.mousePosition) && + !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + } + + // forward arrow light + if (hasOutgoingForwardSegment) { + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && _hoveredNode == nodeId); + + GUI.color = guiColor; + + var offsetLight = lightWidth; + + if (hasOutgoingRightSegment) + offsetLight += lightWidth; + + var myRect6 = + new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + + drawStraightLightTexture(liveSegmentLight.LightMain, myRect6); + + if (myRect6.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 4; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeMainLight(); + } + } + + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 0); + + float numOffset; + + if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } + + var myRectCounterNum = + new Rect( + offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - + pedestrianWidth + 5f * zoom - + (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth), + offsetScreenPos.y - numOffset, counterSize, counterSize); + + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (myRectCounterNum.Contains(Event.current.mousePosition) && + !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 4; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + } + + // right arrow light + if (hasOutgoingRightSegment) { + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 5 && _hoveredNode == nodeId); + + GUI.color = guiColor; + + var rect6 = + new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + + drawRightLightTexture(liveSegmentLight.LightRight, rect6); + + if (rect6.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 5; + _hoveredNode = nodeId; + hoveredSegment = true; + + if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { + liveSegmentLight.ChangeRightLight(); + } + } + + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 2); + + float numOffset; + + if (liveSegmentLight.LightRight == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } + + var myRectCounterNum = + new Rect( + offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - + pedestrianWidth + 5f * zoom - + (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), + offsetScreenPos.y - numOffset, counterSize, counterSize); + + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + + if (myRectCounterNum.Contains(Event.current.mousePosition) && + !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 5; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } + } + break; + } // end switch liveSegmentLight.CurrentMode + } // end foreach light + } // end foreach segment + } // end foreach node + + if (!hoveredSegment) { + _hoveredButton[0] = 0; + _hoveredButton[1] = 0; + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs index 05fd50643..1122de7cb 100644 --- a/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs +++ b/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs @@ -13,6 +13,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + public class ToggleTrafficLightsTool : SubTool { public ToggleTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { diff --git a/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs b/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs index 9e54a5ed1..28a66ceaa 100644 --- a/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs +++ b/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs @@ -1,354 +1,355 @@ -using ColossalFramework; -using ColossalFramework.Math; -using ColossalFramework.UI; -using GenericGameBridge.Service; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using TrafficManager.TrafficLight; -using TrafficManager.Util; -using UnityEngine; -using static TrafficManager.UI.TrafficManagerTool; -using static TrafficManager.Util.SegmentLaneTraverser; - -namespace TrafficManager.UI.SubTools { - public class VehicleRestrictionsTool : SubTool { - private static ExtVehicleType[] roadVehicleTypes = new ExtVehicleType[] { ExtVehicleType.PassengerCar, ExtVehicleType.Bus, ExtVehicleType.Taxi, ExtVehicleType.CargoTruck, ExtVehicleType.Service, ExtVehicleType.Emergency }; - private static ExtVehicleType[] railVehicleTypes = new ExtVehicleType[] { ExtVehicleType.PassengerTrain, ExtVehicleType.CargoTrain }; - private readonly float vehicleRestrictionsSignSize = 80f; - private bool _cursorInSecondaryPanel; - private bool overlayHandleHovered; - private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 620, 100)); - private HashSet currentRestrictedSegmentIds; - - public VehicleRestrictionsTool(TrafficManagerTool mainTool) : base(mainTool) { - currentRestrictedSegmentIds = new HashSet(); - } - - public override void OnActivate() { - _cursorInSecondaryPanel = false; - RefreshCurrentRestrictedSegmentIds(); - } - - private void RefreshCurrentRestrictedSegmentIds(ushort forceSegmentId=0) { - if (forceSegmentId == 0) { - currentRestrictedSegmentIds.Clear(); - } else { - currentRestrictedSegmentIds.Remove(forceSegmentId); - } - - for (uint segmentId = (forceSegmentId == 0 ? 1u : forceSegmentId); segmentId <= (forceSegmentId == 0 ? NetManager.MAX_SEGMENT_COUNT - 1 : forceSegmentId); ++segmentId) { - if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) { - continue; - } - - if (VehicleRestrictionsManager.Instance.HasSegmentRestrictions((ushort)segmentId)) - currentRestrictedSegmentIds.Add((ushort)segmentId); - } - } - - public override void Cleanup() { - - } - - public override void Initialize() { - base.Initialize(); - Cleanup(); - if (Options.vehicleRestrictionsOverlay) { - RefreshCurrentRestrictedSegmentIds(); - } else { - currentRestrictedSegmentIds.Clear(); - } - } - - public override bool IsCursorInPanel() { - return base.IsCursorInPanel() || _cursorInSecondaryPanel; - } - - public override void OnPrimaryClickOverlay() { - //Log._Debug($"Restrictions: {HoveredSegmentId} {overlayHandleHovered}"); - if (HoveredSegmentId == 0) return; - if (overlayHandleHovered) return; - - SelectedSegmentId = HoveredSegmentId; - currentRestrictedSegmentIds.Add(SelectedSegmentId); - MainTool.CheckClicked(); // TODO do we need that? - } - - public override void OnSecondaryClickOverlay() { - if (!IsCursorInPanel()) { - SelectedSegmentId = 0; - } - } - - public override void OnToolGUI(Event e) { - base.OnToolGUI(e); - - if (SelectedSegmentId != 0) { - _cursorInSecondaryPanel = false; - - windowRect = GUILayout.Window(255, windowRect, _guiVehicleRestrictionsWindow, Translation.GetString("Vehicle_restrictions"), WindowStyle); - _cursorInSecondaryPanel = windowRect.Contains(Event.current.mousePosition); - - //overlayHandleHovered = false; - } - //ShowSigns(false); - } - - public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { - //Log._Debug($"Restrictions overlay {_cursorInSecondaryPanel} {HoveredNodeId} {SelectedNodeId} {HoveredSegmentId} {SelectedSegmentId}"); - - if (SelectedSegmentId != 0) - NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], MainTool.GetToolColor(true, false), MainTool.GetToolColor(true, false)); - - if (_cursorInSecondaryPanel) - return; - - if (HoveredSegmentId != 0 && HoveredSegmentId != SelectedSegmentId && !overlayHandleHovered) { - NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId], MainTool.GetToolColor(false, false), MainTool.GetToolColor(false, false)); - } - } - - public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { - if (viewOnly && !Options.vehicleRestrictionsOverlay) - return; - - ShowSigns(viewOnly); - } - - private void ShowSigns(bool viewOnly) { - Vector3 camPos = Camera.main.transform.position; - NetManager netManager = Singleton.instance; - ushort updatedSegmentId = 0; - bool handleHovered = false; - foreach (ushort segmentId in currentRestrictedSegmentIds) { - if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { - continue; - } - - var segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - - Vector3 centerPos = netManager.m_segments.m_buffer[segmentId].m_bounds.center; - Vector3 screenPos; - bool visible = MainTool.WorldToScreenPoint(centerPos, out screenPos); - - if (!visible) - continue; - - if ((netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos).magnitude > TrafficManagerTool.MaxOverlayDistance) - continue; // do not draw if too distant - - // draw vehicle restrictions - bool update; - if (drawVehicleRestrictionHandles(segmentId, ref netManager.m_segments.m_buffer[segmentId], viewOnly || segmentId != SelectedSegmentId, out update)) - handleHovered = true; - - if (update) { - updatedSegmentId = segmentId; - } - } - overlayHandleHovered = handleHovered; - - if (updatedSegmentId != 0) { - RefreshCurrentRestrictedSegmentIds(updatedSegmentId); - } - } - - private void _guiVehicleRestrictionsWindow(int num) { - if (GUILayout.Button(Translation.GetString("Invert"))) { - // invert pattern - - NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; - IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane - foreach (LanePos laneData in sortedLanes) { - uint laneId = laneData.laneId; - byte laneIndex = laneData.laneIndex; - NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; - - ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); - - if (baseMask == ExtVehicleType.None) - continue; +namespace TrafficManager.UI.SubTools { + using System.Collections.Generic; + using API.Traffic.Enums; + using ColossalFramework; + using GenericGameBridge.Service; + using Manager.Impl; + using State; + using Util; + using UnityEngine; + using static Util.SegmentLaneTraverser; + + public class VehicleRestrictionsTool : SubTool { + private static ExtVehicleType[] roadVehicleTypes = { + ExtVehicleType.PassengerCar, ExtVehicleType.Bus, ExtVehicleType.Taxi, ExtVehicleType.CargoTruck, + ExtVehicleType.Service, ExtVehicleType.Emergency + }; + + private static ExtVehicleType[] railVehicleTypes = { + ExtVehicleType.PassengerTrain, ExtVehicleType.CargoTrain + }; + + private readonly float vehicleRestrictionsSignSize = 80f; + + private bool _cursorInSecondaryPanel; + + private bool overlayHandleHovered; + + private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 620, 100)); + + private HashSet currentRestrictedSegmentIds; + + public VehicleRestrictionsTool(TrafficManagerTool mainTool) : base(mainTool) { + currentRestrictedSegmentIds = new HashSet(); + } + + public override void OnActivate() { + _cursorInSecondaryPanel = false; + RefreshCurrentRestrictedSegmentIds(); + } + + private void RefreshCurrentRestrictedSegmentIds(ushort forceSegmentId=0) { + if (forceSegmentId == 0) { + currentRestrictedSegmentIds.Clear(); + } else { + currentRestrictedSegmentIds.Remove(forceSegmentId); + } + + for (uint segmentId = (forceSegmentId == 0 ? 1u : forceSegmentId); segmentId <= (forceSegmentId == 0 ? NetManager.MAX_SEGMENT_COUNT - 1 : forceSegmentId); ++segmentId) { + if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) { + continue; + } + + if (VehicleRestrictionsManager.Instance.HasSegmentRestrictions((ushort)segmentId)) + currentRestrictedSegmentIds.Add((ushort)segmentId); + } + } + + public override void Cleanup() { + + } + + public override void Initialize() { + base.Initialize(); + Cleanup(); + if (Options.vehicleRestrictionsOverlay) { + RefreshCurrentRestrictedSegmentIds(); + } else { + currentRestrictedSegmentIds.Clear(); + } + } + + public override bool IsCursorInPanel() { + return base.IsCursorInPanel() || _cursorInSecondaryPanel; + } + + public override void OnPrimaryClickOverlay() { + //Log._Debug($"Restrictions: {HoveredSegmentId} {overlayHandleHovered}"); + if (HoveredSegmentId == 0) return; + if (overlayHandleHovered) return; + + SelectedSegmentId = HoveredSegmentId; + currentRestrictedSegmentIds.Add(SelectedSegmentId); + MainTool.CheckClicked(); // TODO do we need that? + } + + public override void OnSecondaryClickOverlay() { + if (!IsCursorInPanel()) { + SelectedSegmentId = 0; + } + } + + public override void OnToolGUI(Event e) { + base.OnToolGUI(e); + + if (SelectedSegmentId != 0) { + _cursorInSecondaryPanel = false; + + windowRect = GUILayout.Window(255, windowRect, _guiVehicleRestrictionsWindow, Translation.GetString("Vehicle_restrictions"), WindowStyle); + _cursorInSecondaryPanel = windowRect.Contains(Event.current.mousePosition); + + //overlayHandleHovered = false; + } + //ShowSigns(false); + } + + public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { + //Log._Debug($"Restrictions overlay {_cursorInSecondaryPanel} {HoveredNodeId} {SelectedNodeId} {HoveredSegmentId} {SelectedSegmentId}"); + + if (SelectedSegmentId != 0) + NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], MainTool.GetToolColor(true, false), MainTool.GetToolColor(true, false)); + + if (_cursorInSecondaryPanel) + return; + + if (HoveredSegmentId != 0 && HoveredSegmentId != SelectedSegmentId && !overlayHandleHovered) { + NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId], MainTool.GetToolColor(false, false), MainTool.GetToolColor(false, false)); + } + } + + public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { + if (viewOnly && !Options.vehicleRestrictionsOverlay) + return; + + ShowSigns(viewOnly); + } + + private void ShowSigns(bool viewOnly) { + Vector3 camPos = Camera.main.transform.position; + NetManager netManager = Singleton.instance; + ushort updatedSegmentId = 0; + bool handleHovered = false; + foreach (ushort segmentId in currentRestrictedSegmentIds) { + if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + continue; + } + + var segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + + Vector3 centerPos = netManager.m_segments.m_buffer[segmentId].m_bounds.center; + Vector3 screenPos; + bool visible = MainTool.WorldToScreenPoint(centerPos, out screenPos); + + if (!visible) + continue; + + if ((netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos).magnitude > TrafficManagerTool.MaxOverlayDistance) + continue; // do not draw if too distant + + // draw vehicle restrictions + bool update; + if (drawVehicleRestrictionHandles(segmentId, ref netManager.m_segments.m_buffer[segmentId], viewOnly || segmentId != SelectedSegmentId, out update)) + handleHovered = true; + + if (update) { + updatedSegmentId = segmentId; + } + } + overlayHandleHovered = handleHovered; + + if (updatedSegmentId != 0) { + RefreshCurrentRestrictedSegmentIds(updatedSegmentId); + } + } + + private void _guiVehicleRestrictionsWindow(int num) { + if (GUILayout.Button(Translation.GetString("Invert"))) { + // invert pattern + + NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; + IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane + foreach (LanePos laneData in sortedLanes) { + uint laneId = laneData.laneId; + byte laneIndex = laneData.laneIndex; + NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; + + ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); + + if (baseMask == ExtVehicleType.None) + continue; - ExtVehicleType allowedTypes = VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - allowedTypes = ~(allowedTypes & VehicleRestrictionsManager.EXT_VEHICLE_TYPES) & baseMask; - VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, allowedTypes); - } - RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); - } + ExtVehicleType allowedTypes = VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); + allowedTypes = ~(allowedTypes & VehicleRestrictionsManager.EXT_VEHICLE_TYPES) & baseMask; + VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, allowedTypes); + } + RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); + } - GUILayout.BeginHorizontal(); - if (GUILayout.Button(Translation.GetString("Allow_all_vehicles"))) { - // allow all vehicle types + GUILayout.BeginHorizontal(); + if (GUILayout.Button(Translation.GetString("Allow_all_vehicles"))) { + // allow all vehicle types - NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; - IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane - foreach (LanePos laneData in sortedLanes) { - uint laneId = laneData.laneId; - byte laneIndex = laneData.laneIndex; - NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; + NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; + IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane + foreach (LanePos laneData in sortedLanes) { + uint laneId = laneData.laneId; + byte laneIndex = laneData.laneIndex; + NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; - ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); + ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); - if (baseMask == ExtVehicleType.None) - continue; + if (baseMask == ExtVehicleType.None) + continue; - VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, baseMask); - } - RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); - } + VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, baseMask); + } + RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); + } - if (GUILayout.Button(Translation.GetString("Ban_all_vehicles"))) { - // ban all vehicle types + if (GUILayout.Button(Translation.GetString("Ban_all_vehicles"))) { + // ban all vehicle types - NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; - IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane - foreach (LanePos laneData in sortedLanes) { - uint laneId = laneData.laneId; - byte laneIndex = laneData.laneIndex; - NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; - - ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); - - if (baseMask == ExtVehicleType.None) - continue; - - VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, ~VehicleRestrictionsManager.EXT_VEHICLE_TYPES & baseMask); - } - RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); - } - GUILayout.EndHorizontal(); - - if (GUILayout.Button(Translation.GetString("Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions"))) { - ApplyRestrictionsToAllSegments(); - } - - DragWindow(ref windowRect); - } - - private void ApplyRestrictionsToAllSegments(int? sortedLaneIndex=null) { - NetManager netManager = Singleton.instance; - - NetInfo selectedSegmentInfo = netManager.m_segments.m_buffer[SelectedSegmentId].Info; - ushort selectedStartNodeId = netManager.m_segments.m_buffer[SelectedSegmentId].m_startNode; - ushort selectedEndNodeId = netManager.m_segments.m_buffer[SelectedSegmentId].m_endNode; - - SegmentLaneTraverser.Traverse(SelectedSegmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { - if (data.segVisitData.initial) { - return true; - } - - if (sortedLaneIndex != null && data.sortedLaneIndex != sortedLaneIndex) { - return true; - } - - ushort segmentId = data.segVisitData.curSeg.segmentId; - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - - uint selectedLaneId = data.initLanePos.laneId; - byte selectedLaneIndex = data.initLanePos.laneIndex; - NetInfo.Lane selectedLaneInfo = selectedSegmentInfo.m_lanes[selectedLaneIndex]; - - uint laneId = data.curLanePos.laneId; - byte laneIndex = data.curLanePos.laneIndex; - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - - ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); - if (baseMask == ExtVehicleType.None) { - return true; - } - - // apply restrictions of selected segment & lane - ExtVehicleType mask = ~VehicleRestrictionsManager.EXT_VEHICLE_TYPES & baseMask; // ban all possible controllable vehicles - mask |= VehicleRestrictionsManager.EXT_VEHICLE_TYPES & VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, selectedLaneIndex, selectedLaneInfo, VehicleRestrictionsMode.Configured); // allow all enabled and controllable vehicles - - VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, laneId, mask); - - RefreshCurrentRestrictedSegmentIds(segmentId); - - return true; - }); - } - - private bool drawVehicleRestrictionHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, out bool stateUpdated) { - stateUpdated = false; - - if (viewOnly && !Options.vehicleRestrictionsOverlay && MainTool.GetToolMode() != ToolMode.VehicleRestrictions) - return false; - - Vector3 center = segment.m_bounds.center; - - Vector3 screenPos; - bool visible = MainTool.WorldToScreenPoint(center, out screenPos); - - if (!visible) - return false; - - var camPos = Singleton.instance.m_simulationView.m_position; - var diff = center - camPos; - - if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) - return false; // do not draw if too distant - - int numDirections; - int numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, VehicleRestrictionsManager.VEHICLE_TYPES); - - // draw vehicle restrictions over each lane - NetInfo segmentInfo = segment.Info; - Vector3 yu = (segment.m_endDirection - segment.m_startDirection).normalized; - /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) - yu = -yu;*/ - Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; - float f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates - int maxNumSigns = 0; - if (VehicleRestrictionsManager.Instance.IsRoadSegment(segmentInfo)) - maxNumSigns = roadVehicleTypes.Length; - else if (VehicleRestrictionsManager.Instance.IsRailSegment(segmentInfo)) - maxNumSigns = railVehicleTypes.Length; - //Vector3 zero = center - 0.5f * (float)(numLanes + numDirections - 1) * f * (xu + yu); // "bottom left" - Vector3 zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu - 0.5f * (float)maxNumSigns * f * yu; // "bottom left" - - /*if (!viewOnly) - Log._Debug($"xu: {xu.ToString()} yu: {yu.ToString()} center: {center.ToString()} zero: {zero.ToString()} numLanes: {numLanes} numDirections: {numDirections}");*/ - - uint x = 0; - var guiColor = GUI.color; - IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(segmentId, ref segment, null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); - bool hovered = false; - HashSet directions = new HashSet(); - int sortedLaneIndex = -1; - foreach (LanePos laneData in sortedLanes) { - ++sortedLaneIndex; - uint laneId = laneData.laneId; - byte laneIndex = laneData.laneIndex; - - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - if (!directions.Contains(laneInfo.m_finalDirection)) { - if (directions.Count > 0) - ++x; // space between different directions - directions.Add(laneInfo.m_finalDirection); - } - - ExtVehicleType[] possibleVehicleTypes = null; - if (VehicleRestrictionsManager.Instance.IsRoadLane(laneInfo)) { - possibleVehicleTypes = roadVehicleTypes; - } else if (VehicleRestrictionsManager.Instance.IsRailLane(laneInfo)) { - possibleVehicleTypes = railVehicleTypes; - } else { - ++x; - continue; - } - - ExtVehicleType allowedTypes = VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - - uint y = 0; + NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; + IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane + foreach (LanePos laneData in sortedLanes) { + uint laneId = laneData.laneId; + byte laneIndex = laneData.laneIndex; + NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; + + ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); + + if (baseMask == ExtVehicleType.None) + continue; + + VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, ~VehicleRestrictionsManager.EXT_VEHICLE_TYPES & baseMask); + } + RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); + } + GUILayout.EndHorizontal(); + + if (GUILayout.Button(Translation.GetString("Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions"))) { + ApplyRestrictionsToAllSegments(); + } + + DragWindow(ref windowRect); + } + + private void ApplyRestrictionsToAllSegments(int? sortedLaneIndex=null) { + NetManager netManager = Singleton.instance; + + NetInfo selectedSegmentInfo = netManager.m_segments.m_buffer[SelectedSegmentId].Info; + ushort selectedStartNodeId = netManager.m_segments.m_buffer[SelectedSegmentId].m_startNode; + ushort selectedEndNodeId = netManager.m_segments.m_buffer[SelectedSegmentId].m_endNode; + + SegmentLaneTraverser.Traverse(SelectedSegmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { + if (data.segVisitData.initial) { + return true; + } + + if (sortedLaneIndex != null && data.sortedLaneIndex != sortedLaneIndex) { + return true; + } + + ushort segmentId = data.segVisitData.curSeg.segmentId; + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + + uint selectedLaneId = data.initLanePos.laneId; + byte selectedLaneIndex = data.initLanePos.laneIndex; + NetInfo.Lane selectedLaneInfo = selectedSegmentInfo.m_lanes[selectedLaneIndex]; + + uint laneId = data.curLanePos.laneId; + byte laneIndex = data.curLanePos.laneIndex; + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + + ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); + if (baseMask == ExtVehicleType.None) { + return true; + } + + // apply restrictions of selected segment & lane + ExtVehicleType mask = ~VehicleRestrictionsManager.EXT_VEHICLE_TYPES & baseMask; // ban all possible controllable vehicles + mask |= VehicleRestrictionsManager.EXT_VEHICLE_TYPES & VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, selectedLaneIndex, selectedLaneInfo, VehicleRestrictionsMode.Configured); // allow all enabled and controllable vehicles + + VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, laneId, mask); + + RefreshCurrentRestrictedSegmentIds(segmentId); + + return true; + }); + } + + private bool drawVehicleRestrictionHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, out bool stateUpdated) { + stateUpdated = false; + + if (viewOnly && !Options.vehicleRestrictionsOverlay && MainTool.GetToolMode() != ToolMode.VehicleRestrictions) + return false; + + Vector3 center = segment.m_bounds.center; + + Vector3 screenPos; + bool visible = MainTool.WorldToScreenPoint(center, out screenPos); + + if (!visible) + return false; + + var camPos = Singleton.instance.m_simulationView.m_position; + var diff = center - camPos; + + if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) + return false; // do not draw if too distant + + int numDirections; + int numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, VehicleRestrictionsManager.VEHICLE_TYPES); + + // draw vehicle restrictions over each lane + NetInfo segmentInfo = segment.Info; + Vector3 yu = (segment.m_endDirection - segment.m_startDirection).normalized; + /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) + yu = -yu;*/ + Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; + float f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates + int maxNumSigns = 0; + if (VehicleRestrictionsManager.Instance.IsRoadSegment(segmentInfo)) + maxNumSigns = roadVehicleTypes.Length; + else if (VehicleRestrictionsManager.Instance.IsRailSegment(segmentInfo)) + maxNumSigns = railVehicleTypes.Length; + //Vector3 zero = center - 0.5f * (float)(numLanes + numDirections - 1) * f * (xu + yu); // "bottom left" + Vector3 zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu - 0.5f * (float)maxNumSigns * f * yu; // "bottom left" + + /*if (!viewOnly) + Log._Debug($"xu: {xu.ToString()} yu: {yu.ToString()} center: {center.ToString()} zero: {zero.ToString()} numLanes: {numLanes} numDirections: {numDirections}");*/ + + uint x = 0; + var guiColor = GUI.color; + IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(segmentId, ref segment, null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); + bool hovered = false; + HashSet directions = new HashSet(); + int sortedLaneIndex = -1; + foreach (LanePos laneData in sortedLanes) { + ++sortedLaneIndex; + uint laneId = laneData.laneId; + byte laneIndex = laneData.laneIndex; + + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + if (!directions.Contains(laneInfo.m_finalDirection)) { + if (directions.Count > 0) + ++x; // space between different directions + directions.Add(laneInfo.m_finalDirection); + } + + ExtVehicleType[] possibleVehicleTypes = null; + if (VehicleRestrictionsManager.Instance.IsRoadLane(laneInfo)) { + possibleVehicleTypes = roadVehicleTypes; + } else if (VehicleRestrictionsManager.Instance.IsRailLane(laneInfo)) { + possibleVehicleTypes = railVehicleTypes; + } else { + ++x; + continue; + } + + ExtVehicleType allowedTypes = VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); + + uint y = 0; #if DEBUGx Vector3 labelCenter = zero + f * (float)x * xu + f * (float)y * yu; // in game coordinates @@ -368,36 +369,36 @@ private bool drawVehicleRestrictionHandles(ushort segmentId, ref NetSegment segm ++y; #endif - foreach (ExtVehicleType vehicleType in possibleVehicleTypes) { - bool allowed = VehicleRestrictionsManager.Instance.IsAllowed(allowedTypes, vehicleType); - if (allowed && viewOnly) - continue; // do not draw allowed vehicles in view-only mode - - bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture(TextureResources.VehicleRestrictionTextures[vehicleType][allowed], camPos, zero, f, xu, yu, x, y, vehicleRestrictionsSignSize, !viewOnly); - if (hoveredHandle) - hovered = true; - - if (hoveredHandle && MainTool.CheckClicked()) { - // toggle vehicle restrictions - //Log._Debug($"Setting vehicle restrictions of segment {segmentId}, lane idx {laneIndex}, {vehicleType.ToString()} to {!allowed}"); - VehicleRestrictionsManager.Instance.ToggleAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType, !allowed); - stateUpdated = true; - - if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { - ApplyRestrictionsToAllSegments(sortedLaneIndex); - } - } - - ++y; - } - - ++x; - } - - guiColor.a = 1f; - GUI.color = guiColor; - - return hovered; - } - } -} + foreach (ExtVehicleType vehicleType in possibleVehicleTypes) { + bool allowed = VehicleRestrictionsManager.Instance.IsAllowed(allowedTypes, vehicleType); + if (allowed && viewOnly) + continue; // do not draw allowed vehicles in view-only mode + + bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture(TextureResources.VehicleRestrictionTextures[vehicleType][allowed], camPos, zero, f, xu, yu, x, y, vehicleRestrictionsSignSize, !viewOnly); + if (hoveredHandle) + hovered = true; + + if (hoveredHandle && MainTool.CheckClicked()) { + // toggle vehicle restrictions + //Log._Debug($"Setting vehicle restrictions of segment {segmentId}, lane idx {laneIndex}, {vehicleType.ToString()} to {!allowed}"); + VehicleRestrictionsManager.Instance.ToggleAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType, !allowed); + stateUpdated = true; + + if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { + ApplyRestrictionsToAllSegments(sortedLaneIndex); + } + } + + ++y; + } + + ++x; + } + + guiColor.a = 1f; + GUI.color = guiColor; + + return hovered; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index f13b8a3a0..e59f9f83c 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -1,286 +1,280 @@ -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; -using TrafficManager.UI; -using TrafficManager.Util; -using UnityEngine; -using static TrafficManager.Traffic.Data.PrioritySegment; - -namespace TrafficManager.UI -{ - public class TextureResources { - public static readonly Texture2D RedLightTexture2D; - public static readonly Texture2D YellowRedLightTexture2D; - public static readonly Texture2D YellowLightTexture2D; - public static readonly Texture2D GreenLightTexture2D; - public static readonly Texture2D RedLightStraightTexture2D; - public static readonly Texture2D YellowLightStraightTexture2D; - public static readonly Texture2D GreenLightStraightTexture2D; - public static readonly Texture2D RedLightRightTexture2D; - public static readonly Texture2D YellowLightRightTexture2D; - public static readonly Texture2D GreenLightRightTexture2D; - public static readonly Texture2D RedLightLeftTexture2D; - public static readonly Texture2D YellowLightLeftTexture2D; - public static readonly Texture2D GreenLightLeftTexture2D; - public static readonly Texture2D RedLightForwardRightTexture2D; - public static readonly Texture2D YellowLightForwardRightTexture2D; - public static readonly Texture2D GreenLightForwardRightTexture2D; - public static readonly Texture2D RedLightForwardLeftTexture2D; - public static readonly Texture2D YellowLightForwardLeftTexture2D; - public static readonly Texture2D GreenLightForwardLeftTexture2D; - public static readonly Texture2D PedestrianRedLightTexture2D; - public static readonly Texture2D PedestrianGreenLightTexture2D; - public static readonly Texture2D LightModeTexture2D; - public static readonly Texture2D LightCounterTexture2D; - public static readonly Texture2D PedestrianModeAutomaticTexture2D; - public static readonly Texture2D PedestrianModeManualTexture2D; - public static readonly IDictionary PrioritySignTextures; - public static readonly Texture2D SignRemoveTexture2D; - public static readonly Texture2D ClockPlayTexture2D; - public static readonly Texture2D ClockPauseTexture2D; - public static readonly Texture2D ClockTestTexture2D; - public static readonly IDictionary SpeedLimitTexturesKmph; - public static readonly IDictionary SpeedLimitTexturesMphUS; - public static readonly IDictionary SpeedLimitTexturesMphUK; - public static readonly IDictionary> VehicleRestrictionTextures; - public static readonly IDictionary VehicleInfoSignTextures; - public static readonly IDictionary ParkingRestrictionTextures; - public static readonly Texture2D LaneChangeForbiddenTexture2D; - public static readonly Texture2D LaneChangeAllowedTexture2D; - public static readonly Texture2D UturnAllowedTexture2D; - public static readonly Texture2D UturnForbiddenTexture2D; - public static readonly Texture2D RightOnRedForbiddenTexture2D; - public static readonly Texture2D RightOnRedAllowedTexture2D; - public static readonly Texture2D LeftOnRedForbiddenTexture2D; - public static readonly Texture2D LeftOnRedAllowedTexture2D; - public static readonly Texture2D EnterBlockedJunctionAllowedTexture2D; - public static readonly Texture2D EnterBlockedJunctionForbiddenTexture2D; - public static readonly Texture2D PedestrianCrossingAllowedTexture2D; - public static readonly Texture2D PedestrianCrossingForbiddenTexture2D; - public static readonly Texture2D MainMenuButtonTexture2D; - public static readonly Texture2D MainMenuButtonsTexture2D; - public static readonly Texture2D NoImageTexture2D; - public static readonly Texture2D RemoveButtonTexture2D; - public static readonly Texture2D WindowBackgroundTexture2D; - - static TextureResources() { - // missing image - NoImageTexture2D = LoadDllResource("noimage.png", 64, 64); - - // main menu icon - MainMenuButtonTexture2D = LoadDllResource("MenuButton.png", 300, 50); - MainMenuButtonTexture2D.name = "TMPE_MainMenuButtonIcon"; - - // main menu buttons - MainMenuButtonsTexture2D = LoadDllResource("mainmenu-btns.png", 960, 30); - MainMenuButtonsTexture2D.name = "TMPE_MainMenuButtons"; - - // simple - RedLightTexture2D = LoadDllResource("light_1_1.png", 103, 243); - YellowRedLightTexture2D = LoadDllResource("light_1_2.png", 103, 243); - GreenLightTexture2D = LoadDllResource("light_1_3.png", 103, 243); - // forward - RedLightStraightTexture2D = LoadDllResource("light_2_1.png", 103, 243); - YellowLightStraightTexture2D = LoadDllResource("light_2_2.png", 103, 243); - GreenLightStraightTexture2D = LoadDllResource("light_2_3.png", 103, 243); - // right - RedLightRightTexture2D = LoadDllResource("light_3_1.png", 103, 243); - YellowLightRightTexture2D = LoadDllResource("light_3_2.png", 103, 243); - GreenLightRightTexture2D = LoadDllResource("light_3_3.png", 103, 243); - // left - RedLightLeftTexture2D = LoadDllResource("light_4_1.png", 103, 243); - YellowLightLeftTexture2D = LoadDllResource("light_4_2.png", 103, 243); - GreenLightLeftTexture2D = LoadDllResource("light_4_3.png", 103, 243); - // forwardright - RedLightForwardRightTexture2D = LoadDllResource("light_5_1.png", 103, 243); - YellowLightForwardRightTexture2D = LoadDllResource("light_5_2.png", 103, 243); - GreenLightForwardRightTexture2D = LoadDllResource("light_5_3.png", 103, 243); - // forwardleft - RedLightForwardLeftTexture2D = LoadDllResource("light_6_1.png", 103, 243); - YellowLightForwardLeftTexture2D = LoadDllResource("light_6_2.png", 103, 243); - GreenLightForwardLeftTexture2D = LoadDllResource("light_6_3.png", 103, 243); - // yellow - YellowLightTexture2D = LoadDllResource("light_yellow.png", 103, 243); - // pedestrian - PedestrianRedLightTexture2D = LoadDllResource("pedestrian_light_1.png", 73, 123); - PedestrianGreenLightTexture2D = LoadDllResource("pedestrian_light_2.png", 73, 123); - // light mode - LightModeTexture2D = - LoadDllResource(Translation.GetTranslatedFileName("light_mode.png"), 103, 95); - LightCounterTexture2D = - LoadDllResource(Translation.GetTranslatedFileName("light_counter.png"), 103, 95); - // pedestrian mode - PedestrianModeAutomaticTexture2D = LoadDllResource("pedestrian_mode_1.png", 73, 70); - PedestrianModeManualTexture2D = LoadDllResource("pedestrian_mode_2.png", 73, 73); - - // priority signs - PrioritySignTextures = new TinyDictionary(); - PrioritySignTextures[PriorityType.None] = LoadDllResource("sign_none.png", 200, 200); - PrioritySignTextures[PriorityType.Main] = LoadDllResource("sign_priority.png", 200, 200); - PrioritySignTextures[PriorityType.Stop] = LoadDllResource("sign_stop.png", 200, 200); - PrioritySignTextures[PriorityType.Yield] = LoadDllResource("sign_yield.png", 200, 200); - - // delete priority sign - SignRemoveTexture2D = LoadDllResource("remove_signs.png", 256, 256); - - // timer - ClockPlayTexture2D = LoadDllResource("clock_play.png", 512, 512); - ClockPauseTexture2D = LoadDllResource("clock_pause.png", 512, 512); - ClockTestTexture2D = LoadDllResource("clock_test.png", 512, 512); - - // TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor - SpeedLimitTexturesKmph = new TinyDictionary(); - SpeedLimitTexturesMphUS = new TinyDictionary(); - SpeedLimitTexturesMphUK = new TinyDictionary(); - - // Load shared speed limit signs for Kmph and Mph - // Assumes that signs from 0 to 140 with step 5 exist, 0 denotes no limit sign - for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) { - var resource = LoadDllResource($"SpeedLimits.Kmh.{speedLimit}.png", 200, 200); - SpeedLimitTexturesKmph.Add(speedLimit, resource ?? SpeedLimitTexturesKmph[5]); - } - // Signs from 0 to 90 for MPH - for (var speedLimit = 0; speedLimit <= 90; speedLimit += 5) { - // Load US textures, they are rectangular - var resourceUs = LoadDllResource($"SpeedLimits.Mph_US.{speedLimit}.png", 200, 250); - SpeedLimitTexturesMphUS.Add(speedLimit, resourceUs ?? SpeedLimitTexturesMphUS[5]); - // Load UK textures, they are square - var resourceUk = LoadDllResource($"SpeedLimits.Mph_UK.{speedLimit}.png", 200, 200); - SpeedLimitTexturesMphUK.Add(speedLimit, resourceUk ?? SpeedLimitTexturesMphUK[5]); - } - - VehicleRestrictionTextures = new TinyDictionary>(); - VehicleRestrictionTextures[ExtVehicleType.Bus] = new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.CargoTrain] = new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.CargoTruck] = new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.Emergency] = new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.PassengerCar] = new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.PassengerTrain] = - new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.Service] = new TinyDictionary(); - VehicleRestrictionTextures[ExtVehicleType.Taxi] = new TinyDictionary(); - - foreach (KeyValuePair> e in - VehicleRestrictionTextures) { - foreach (bool b in new bool[] {false, true}) { - string suffix = b ? "allowed" : "forbidden"; - e.Value[b] = - LoadDllResource(e.Key.ToString().ToLower() + "_" + suffix + ".png", 200, - 200); - } - } - - ParkingRestrictionTextures = new TinyDictionary(); - ParkingRestrictionTextures[true] = LoadDllResource("parking_allowed.png", 200, 200); - ParkingRestrictionTextures[false] = LoadDllResource("parking_disallowed.png", 200, 200); - - LaneChangeAllowedTexture2D = LoadDllResource("lanechange_allowed.png", 200, 200); - LaneChangeForbiddenTexture2D = LoadDllResource("lanechange_forbidden.png", 200, 200); - - UturnAllowedTexture2D = LoadDllResource("uturn_allowed.png", 200, 200); - UturnForbiddenTexture2D = LoadDllResource("uturn_forbidden.png", 200, 200); - - RightOnRedAllowedTexture2D = LoadDllResource("right_on_red_allowed.png", 200, 200); - RightOnRedForbiddenTexture2D = LoadDllResource("right_on_red_forbidden.png", 200, 200); - LeftOnRedAllowedTexture2D = LoadDllResource("left_on_red_allowed.png", 200, 200); - LeftOnRedForbiddenTexture2D = LoadDllResource("left_on_red_forbidden.png", 200, 200); - - EnterBlockedJunctionAllowedTexture2D = LoadDllResource("enterblocked_allowed.png", 200, 200); - EnterBlockedJunctionForbiddenTexture2D = - LoadDllResource("enterblocked_forbidden.png", 200, 200); - - PedestrianCrossingAllowedTexture2D = LoadDllResource("crossing_allowed.png", 200, 200); - PedestrianCrossingForbiddenTexture2D = LoadDllResource("crossing_forbidden.png", 200, 200); - - VehicleInfoSignTextures = new TinyDictionary(); - VehicleInfoSignTextures[ExtVehicleType.Bicycle] = - LoadDllResource("bicycle_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.Bus] = LoadDllResource("bus_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.CargoTrain] = - LoadDllResource("cargotrain_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.CargoTruck] = - LoadDllResource("cargotruck_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.Emergency] = - LoadDllResource("emergency_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.PassengerCar] = - LoadDllResource("passengercar_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.PassengerTrain] = - LoadDllResource("passengertrain_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.RailVehicle] = - VehicleInfoSignTextures[ExtVehicleType.PassengerTrain]; - VehicleInfoSignTextures[ExtVehicleType.Service] = - LoadDllResource("service_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.Taxi] = LoadDllResource("taxi_infosign.png", 449, 411); - VehicleInfoSignTextures[ExtVehicleType.Tram] = LoadDllResource("tram_infosign.png", 449, 411); - - RemoveButtonTexture2D = LoadDllResource("remove-btn.png", 150, 30); - - WindowBackgroundTexture2D = LoadDllResource("WindowBackground.png", 16, 60); - } - - /// - /// Given speed limit, round it up to nearest Kmph or Mph and produce a texture - /// - /// Ingame speed - /// The texture, hopefully it existed - public static Texture2D GetSpeedLimitTexture(float speedLimit) { - var m = GlobalConfig.Instance.Main; - var unit = m.DisplaySpeedLimitsMph ? SpeedUnit.Mph : SpeedUnit.Kmph; - return GetSpeedLimitTexture(speedLimit, m.MphRoadSignStyle, unit); - } - - /// - /// Given the float speed, style and MPH option return a texture to render. - /// - /// float speed - /// Signs theme - /// Mph or km/h - /// - public static Texture2D GetSpeedLimitTexture(float speedLimit, MphSignStyle mphStyle, SpeedUnit unit) { - // Select the source for the textures based on unit and the theme - var mph = unit == SpeedUnit.Mph; - var textures = SpeedLimitTexturesKmph; - if (mph) { - switch (mphStyle) { - case MphSignStyle.SquareUS: - textures = SpeedLimitTexturesMphUS; - break; - case MphSignStyle.RoundUK: - textures = SpeedLimitTexturesMphUK; - break; - case MphSignStyle.RoundGerman: - // Do nothing, this is the default above - break; - } - } - - // Trim the range - if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { - return textures[0]; - } - - // Round to nearest 5 MPH or nearest 10 km/h - var index = mph ? SpeedLimit.ToMphRounded(speedLimit) : SpeedLimit.ToKmphRounded(speedLimit); - - // Trim the index since 140 km/h / 90 MPH is the max sign we have - var upper = mph ? SpeedLimit.UPPER_MPH : SpeedLimit.UPPER_KMPH; +namespace TrafficManager.UI { + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using API.Traffic.Enums; + using CSUtil.Commons; + using Manager.Impl; + using State; + using Traffic.Data; + using UnityEngine; + using Util; + + public class TextureResources { + public static readonly Texture2D RedLightTexture2D; + public static readonly Texture2D YellowRedLightTexture2D; + public static readonly Texture2D YellowLightTexture2D; + public static readonly Texture2D GreenLightTexture2D; + public static readonly Texture2D RedLightStraightTexture2D; + public static readonly Texture2D YellowLightStraightTexture2D; + public static readonly Texture2D GreenLightStraightTexture2D; + public static readonly Texture2D RedLightRightTexture2D; + public static readonly Texture2D YellowLightRightTexture2D; + public static readonly Texture2D GreenLightRightTexture2D; + public static readonly Texture2D RedLightLeftTexture2D; + public static readonly Texture2D YellowLightLeftTexture2D; + public static readonly Texture2D GreenLightLeftTexture2D; + public static readonly Texture2D RedLightForwardRightTexture2D; + public static readonly Texture2D YellowLightForwardRightTexture2D; + public static readonly Texture2D GreenLightForwardRightTexture2D; + public static readonly Texture2D RedLightForwardLeftTexture2D; + public static readonly Texture2D YellowLightForwardLeftTexture2D; + public static readonly Texture2D GreenLightForwardLeftTexture2D; + public static readonly Texture2D PedestrianRedLightTexture2D; + public static readonly Texture2D PedestrianGreenLightTexture2D; + public static readonly Texture2D LightModeTexture2D; + public static readonly Texture2D LightCounterTexture2D; + public static readonly Texture2D PedestrianModeAutomaticTexture2D; + public static readonly Texture2D PedestrianModeManualTexture2D; + public static readonly IDictionary PrioritySignTextures; + public static readonly Texture2D SignRemoveTexture2D; + public static readonly Texture2D ClockPlayTexture2D; + public static readonly Texture2D ClockPauseTexture2D; + public static readonly Texture2D ClockTestTexture2D; + public static readonly IDictionary SpeedLimitTexturesKmph; + public static readonly IDictionary SpeedLimitTexturesMphUS; + public static readonly IDictionary SpeedLimitTexturesMphUK; + public static readonly IDictionary> VehicleRestrictionTextures; + public static readonly IDictionary VehicleInfoSignTextures; + public static readonly IDictionary ParkingRestrictionTextures; + public static readonly Texture2D LaneChangeForbiddenTexture2D; + public static readonly Texture2D LaneChangeAllowedTexture2D; + public static readonly Texture2D UturnAllowedTexture2D; + public static readonly Texture2D UturnForbiddenTexture2D; + public static readonly Texture2D RightOnRedForbiddenTexture2D; + public static readonly Texture2D RightOnRedAllowedTexture2D; + public static readonly Texture2D LeftOnRedForbiddenTexture2D; + public static readonly Texture2D LeftOnRedAllowedTexture2D; + public static readonly Texture2D EnterBlockedJunctionAllowedTexture2D; + public static readonly Texture2D EnterBlockedJunctionForbiddenTexture2D; + public static readonly Texture2D PedestrianCrossingAllowedTexture2D; + public static readonly Texture2D PedestrianCrossingForbiddenTexture2D; + public static readonly Texture2D MainMenuButtonTexture2D; + public static readonly Texture2D MainMenuButtonsTexture2D; + public static readonly Texture2D NoImageTexture2D; + public static readonly Texture2D RemoveButtonTexture2D; + public static readonly Texture2D WindowBackgroundTexture2D; + + static TextureResources() { + // missing image + NoImageTexture2D = LoadDllResource("noimage.png", 64, 64); + + // main menu icon + MainMenuButtonTexture2D = LoadDllResource("MenuButton.png", 300, 50); + MainMenuButtonTexture2D.name = "TMPE_MainMenuButtonIcon"; + + // main menu buttons + MainMenuButtonsTexture2D = LoadDllResource("mainmenu-btns.png", 960, 30); + MainMenuButtonsTexture2D.name = "TMPE_MainMenuButtons"; + + // simple + RedLightTexture2D = LoadDllResource("light_1_1.png", 103, 243); + YellowRedLightTexture2D = LoadDllResource("light_1_2.png", 103, 243); + GreenLightTexture2D = LoadDllResource("light_1_3.png", 103, 243); + // forward + RedLightStraightTexture2D = LoadDllResource("light_2_1.png", 103, 243); + YellowLightStraightTexture2D = LoadDllResource("light_2_2.png", 103, 243); + GreenLightStraightTexture2D = LoadDllResource("light_2_3.png", 103, 243); + // right + RedLightRightTexture2D = LoadDllResource("light_3_1.png", 103, 243); + YellowLightRightTexture2D = LoadDllResource("light_3_2.png", 103, 243); + GreenLightRightTexture2D = LoadDllResource("light_3_3.png", 103, 243); + // left + RedLightLeftTexture2D = LoadDllResource("light_4_1.png", 103, 243); + YellowLightLeftTexture2D = LoadDllResource("light_4_2.png", 103, 243); + GreenLightLeftTexture2D = LoadDllResource("light_4_3.png", 103, 243); + // forwardright + RedLightForwardRightTexture2D = LoadDllResource("light_5_1.png", 103, 243); + YellowLightForwardRightTexture2D = LoadDllResource("light_5_2.png", 103, 243); + GreenLightForwardRightTexture2D = LoadDllResource("light_5_3.png", 103, 243); + // forwardleft + RedLightForwardLeftTexture2D = LoadDllResource("light_6_1.png", 103, 243); + YellowLightForwardLeftTexture2D = LoadDllResource("light_6_2.png", 103, 243); + GreenLightForwardLeftTexture2D = LoadDllResource("light_6_3.png", 103, 243); + // yellow + YellowLightTexture2D = LoadDllResource("light_yellow.png", 103, 243); + // pedestrian + PedestrianRedLightTexture2D = LoadDllResource("pedestrian_light_1.png", 73, 123); + PedestrianGreenLightTexture2D = LoadDllResource("pedestrian_light_2.png", 73, 123); + // light mode + LightModeTexture2D = + LoadDllResource(Translation.GetTranslatedFileName("light_mode.png"), 103, 95); + LightCounterTexture2D = + LoadDllResource(Translation.GetTranslatedFileName("light_counter.png"), 103, 95); + // pedestrian mode + PedestrianModeAutomaticTexture2D = LoadDllResource("pedestrian_mode_1.png", 73, 70); + PedestrianModeManualTexture2D = LoadDllResource("pedestrian_mode_2.png", 73, 73); + + // priority signs + PrioritySignTextures = new TinyDictionary(); + PrioritySignTextures[PriorityType.None] = LoadDllResource("sign_none.png", 200, 200); + PrioritySignTextures[PriorityType.Main] = LoadDllResource("sign_priority.png", 200, 200); + PrioritySignTextures[PriorityType.Stop] = LoadDllResource("sign_stop.png", 200, 200); + PrioritySignTextures[PriorityType.Yield] = LoadDllResource("sign_yield.png", 200, 200); + + // delete priority sign + SignRemoveTexture2D = LoadDllResource("remove_signs.png", 256, 256); + + // timer + ClockPlayTexture2D = LoadDllResource("clock_play.png", 512, 512); + ClockPauseTexture2D = LoadDllResource("clock_pause.png", 512, 512); + ClockTestTexture2D = LoadDllResource("clock_test.png", 512, 512); + + // TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor + SpeedLimitTexturesKmph = new TinyDictionary(); + SpeedLimitTexturesMphUS = new TinyDictionary(); + SpeedLimitTexturesMphUK = new TinyDictionary(); + + // Load shared speed limit signs for Kmph and Mph + // Assumes that signs from 0 to 140 with step 5 exist, 0 denotes no limit sign + for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) { + var resource = LoadDllResource($"SpeedLimits.Kmh.{speedLimit}.png", 200, 200); + SpeedLimitTexturesKmph.Add(speedLimit, resource ?? SpeedLimitTexturesKmph[5]); + } + // Signs from 0 to 90 for MPH + for (var speedLimit = 0; speedLimit <= 90; speedLimit += 5) { + // Load US textures, they are rectangular + var resourceUs = LoadDllResource($"SpeedLimits.Mph_US.{speedLimit}.png", 200, 250); + SpeedLimitTexturesMphUS.Add(speedLimit, resourceUs ?? SpeedLimitTexturesMphUS[5]); + // Load UK textures, they are square + var resourceUk = LoadDllResource($"SpeedLimits.Mph_UK.{speedLimit}.png", 200, 200); + SpeedLimitTexturesMphUK.Add(speedLimit, resourceUk ?? SpeedLimitTexturesMphUK[5]); + } + + VehicleRestrictionTextures = new TinyDictionary>(); + VehicleRestrictionTextures[ExtVehicleType.Bus] = new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.CargoTrain] = new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.CargoTruck] = new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.Emergency] = new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.PassengerCar] = new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.PassengerTrain] = + new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.Service] = new TinyDictionary(); + VehicleRestrictionTextures[ExtVehicleType.Taxi] = new TinyDictionary(); + + foreach (KeyValuePair> e in + VehicleRestrictionTextures) { + foreach (bool b in new bool[] {false, true}) { + string suffix = b ? "allowed" : "forbidden"; + e.Value[b] = + LoadDllResource(e.Key.ToString().ToLower() + "_" + suffix + ".png", 200, + 200); + } + } + + ParkingRestrictionTextures = new TinyDictionary(); + ParkingRestrictionTextures[true] = LoadDllResource("parking_allowed.png", 200, 200); + ParkingRestrictionTextures[false] = LoadDllResource("parking_disallowed.png", 200, 200); + + LaneChangeAllowedTexture2D = LoadDllResource("lanechange_allowed.png", 200, 200); + LaneChangeForbiddenTexture2D = LoadDllResource("lanechange_forbidden.png", 200, 200); + + UturnAllowedTexture2D = LoadDllResource("uturn_allowed.png", 200, 200); + UturnForbiddenTexture2D = LoadDllResource("uturn_forbidden.png", 200, 200); + + RightOnRedAllowedTexture2D = LoadDllResource("right_on_red_allowed.png", 200, 200); + RightOnRedForbiddenTexture2D = LoadDllResource("right_on_red_forbidden.png", 200, 200); + LeftOnRedAllowedTexture2D = LoadDllResource("left_on_red_allowed.png", 200, 200); + LeftOnRedForbiddenTexture2D = LoadDllResource("left_on_red_forbidden.png", 200, 200); + + EnterBlockedJunctionAllowedTexture2D = LoadDllResource("enterblocked_allowed.png", 200, 200); + EnterBlockedJunctionForbiddenTexture2D = + LoadDllResource("enterblocked_forbidden.png", 200, 200); + + PedestrianCrossingAllowedTexture2D = LoadDllResource("crossing_allowed.png", 200, 200); + PedestrianCrossingForbiddenTexture2D = LoadDllResource("crossing_forbidden.png", 200, 200); + + VehicleInfoSignTextures = new TinyDictionary(); + VehicleInfoSignTextures[ExtVehicleType.Bicycle] = + LoadDllResource("bicycle_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.Bus] = LoadDllResource("bus_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.CargoTrain] = + LoadDllResource("cargotrain_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.CargoTruck] = + LoadDllResource("cargotruck_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.Emergency] = + LoadDllResource("emergency_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.PassengerCar] = + LoadDllResource("passengercar_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.PassengerTrain] = + LoadDllResource("passengertrain_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.RailVehicle] = + VehicleInfoSignTextures[ExtVehicleType.PassengerTrain]; + VehicleInfoSignTextures[ExtVehicleType.Service] = + LoadDllResource("service_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.Taxi] = LoadDllResource("taxi_infosign.png", 449, 411); + VehicleInfoSignTextures[ExtVehicleType.Tram] = LoadDllResource("tram_infosign.png", 449, 411); + + RemoveButtonTexture2D = LoadDllResource("remove-btn.png", 150, 30); + + WindowBackgroundTexture2D = LoadDllResource("WindowBackground.png", 16, 60); + } + + /// + /// Given speed limit, round it up to nearest Kmph or Mph and produce a texture + /// + /// Ingame speed + /// The texture, hopefully it existed + public static Texture2D GetSpeedLimitTexture(float speedLimit) { + var m = GlobalConfig.Instance.Main; + var unit = m.DisplaySpeedLimitsMph ? SpeedUnit.Mph : SpeedUnit.Kmph; + return GetSpeedLimitTexture(speedLimit, m.MphRoadSignStyle, unit); + } + + /// + /// Given the float speed, style and MPH option return a texture to render. + /// + /// float speed + /// Signs theme + /// Mph or km/h + /// + public static Texture2D GetSpeedLimitTexture(float speedLimit, MphSignStyle mphStyle, SpeedUnit unit) { + // Select the source for the textures based on unit and the theme + var mph = unit == SpeedUnit.Mph; + var textures = SpeedLimitTexturesKmph; + if (mph) { + switch (mphStyle) { + case MphSignStyle.SquareUS: + textures = SpeedLimitTexturesMphUS; + break; + case MphSignStyle.RoundUK: + textures = SpeedLimitTexturesMphUK; + break; + case MphSignStyle.RoundGerman: + // Do nothing, this is the default above + break; + } + } + + // Trim the range + if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { + return textures[0]; + } + + // Round to nearest 5 MPH or nearest 10 km/h + var index = mph ? SpeedLimit.ToMphRounded(speedLimit) : SpeedLimit.ToKmphRounded(speedLimit); + + // Trim the index since 140 km/h / 90 MPH is the max sign we have + var upper = mph ? SpeedLimit.UPPER_MPH : SpeedLimit.UPPER_KMPH; #if DEBUG if (index > upper) { - Log.Info($"Trimming speed={speedLimit} index={index} to {upper}"); - } + Log.Info($"Trimming speed={speedLimit} index={index} to {upper}"); + } #endif - var trimIndex = Math.Min(upper, Math.Max((ushort)0, index)); - return textures[trimIndex]; - } + var trimIndex = Math.Min(upper, Math.Max((ushort)0, index)); + return textures[trimIndex]; + } private static Texture2D LoadDllResource(string resourceName, int width, int height) { @@ -353,5 +347,5 @@ static byte[] ReadToEnd(Stream stream) stream.Position = originalPosition; } } - } + } } \ No newline at end of file diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 743217170..969326c18 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -1,23 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ColossalFramework; -using ColossalFramework.Math; -using ColossalFramework.UI; -using JetBrains.Annotations; -using UnityEngine; -using TrafficManager.State; -using TrafficManager.UI.SubTools; -using TrafficManager.Traffic; -using TrafficManager.Manager; -using TrafficManager.Util; -using TrafficManager.UI.MainMenu; -using CSUtil.Commons; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; - -namespace TrafficManager.UI { +namespace TrafficManager.UI { + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using ColossalFramework.UI; + using CSUtil.Commons; + using JetBrains.Annotations; + using Manager; + using Manager.Impl; + using State; + using Traffic.Data; + using Traffic.Enums; + using UI.MainMenu; + using UI.SubTools; + using Util; + using UnityEngine; + [UsedImplicitly] public class TrafficManagerTool : DefaultTool, IObserver { public struct NodeVisitItem { diff --git a/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs b/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs index 7d6fcd693..8bd0cabc3 100644 --- a/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs +++ b/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs @@ -7,6 +7,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Geometry { + using API.Traffic.Enums; + public interface ISegmentEndGeometry : ISegmentEndId { /// /// Holds the connected node id diff --git a/TLM/TMPE.API/Geometry/ISegmentGeometry.cs b/TLM/TMPE.API/Geometry/ISegmentGeometry.cs index 9dcecb5ab..b511bbc39 100644 --- a/TLM/TMPE.API/Geometry/ISegmentGeometry.cs +++ b/TLM/TMPE.API/Geometry/ISegmentGeometry.cs @@ -5,6 +5,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Geometry { + using API.Traffic.Enums; + public interface ISegmentGeometry { /// /// Holds the id of the managed segment. diff --git a/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs b/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs index c00950fcd..1d89d73bd 100644 --- a/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs +++ b/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs @@ -9,6 +9,8 @@ using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager { + using API.Traffic.Enums; + public interface IAdvancedParkingManager : IFeatureManager { /// /// Determines the color the given building should be colorized with given the current info view mode. diff --git a/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs b/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs index cededb5b8..550ee5461 100644 --- a/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs +++ b/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs @@ -1,24 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +using TrafficManager.Traffic.Enums; using TrafficManager.TrafficLight; namespace TrafficManager.Manager { - public interface ICustomSegmentLightsManager { - // TODO documentation - ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId); - ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add = true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red); - ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode); - void AddNodeLights(ushort nodeId); - void RemoveNodeLights(ushort nodeId); - void RemoveSegmentLights(ushort segmentId); - void RemoveSegmentLight(ushort segmentId, bool startNode); - bool IsSegmentLight(ushort segmentId, bool startNode); - void SetLightMode(ushort segmentId, bool startNode, ExtVehicleType vehicleType, LightMode mode); - bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights); - bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights); - } -} + using API.Traffic.Enums; + using API.TrafficLight; + + public interface ICustomSegmentLightsManager { + // TODO documentation + ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId); + ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add = true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red); + ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode); + void AddNodeLights(ushort nodeId); + void RemoveNodeLights(ushort nodeId); + void RemoveSegmentLights(ushort segmentId); + void RemoveSegmentLight(ushort segmentId, bool startNode); + bool IsSegmentLight(ushort segmentId, bool startNode); + void SetLightMode(ushort segmentId, bool startNode, ExtVehicleType vehicleType, LightMode mode); + bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights); + bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Manager/IExtVehicleManager.cs b/TLM/TMPE.API/Manager/IExtVehicleManager.cs index 86e3963db..0e68b8798 100644 --- a/TLM/TMPE.API/Manager/IExtVehicleManager.cs +++ b/TLM/TMPE.API/Manager/IExtVehicleManager.cs @@ -1,167 +1,165 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +using TrafficManager.Traffic.Enums; using TrafficManager.Traffic.Data; namespace TrafficManager.Manager { - public interface IExtVehicleManager { - /// - /// Extended vehicle data - /// - ExtVehicle[] ExtVehicles { get; } - - /// - /// Handles a released vehicle. - /// - /// vehicle id - /// vehicle data - void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a released vehicle. - /// - /// vehicle - void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData); - - /// - /// Handles a created vehicle. - /// - /// vehicle id - /// vehicle data - void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a created vehicle. - /// - /// vehicle - void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData); - - /// - /// Handles a spawned vehicle. - /// - /// vehicle id - /// vehicle data - void OnSpawnVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a spawned vehicle. - /// - /// vehicle - void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData); - - /// - /// Handles a despawned vehicle. - /// - /// vehicle id - /// vehicle data - void OnDespawnVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a despawned vehicle. - /// - /// vehicle - void OnDespawn(ref ExtVehicle extVehicle); - - /// - /// Handles a vehicle when path-finding starts. - /// - /// vehicle id - /// vehicle data - /// vehicle type to set, optional - /// vehicle type - ExtVehicleType OnStartPathFind(ushort vehicleId, ref Vehicle vehicleData, ExtVehicleType? vehicleType); - - /// - /// Handles a vehicle when path-finding starts. - /// - /// vehicle - /// vehicle data - /// vehicle type to set, optional - /// vehicle type - ExtVehicleType OnStartPathFind(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ExtVehicleType? vehicleType); - - /// - /// Retrieves the driver citizen instance id for the given vehicle. - /// - /// vehicle id - /// vehicle data - /// - ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data); - - /// - /// Updates the vehicle's current path position. - /// - /// vehicle id - /// vehicle data - void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Updates the vehicle's current path position. - /// - /// vehicle - /// vehicle data - /// ext. segment end - /// current path position - /// next path position - void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos); - - /// - /// Unlinks the given vehicle from any segment end that the vehicle is currently linked to. - /// - /// vehicle - void Unlink(ref ExtVehicle extVehicle); - - /// - /// Updates the vehicle's junction transit state to the given value. - /// - /// vehicle - /// junction transit state - void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState); - - /// - /// Determines if the junction transit state has been recently modified - /// - /// /// vehicle - /// true if the junction transit state is considered new, false otherwise - bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle); - - /// - /// Triggers the randomization mechanism that might select a new random value for time-varying vehicle behavior (e.g. speed) - /// - /// ext. vehicle data - /// whether to force generation of a new value - void StepRand(ref ExtVehicle extVehicle, bool force); - - /// - /// Calculates the current randomization value for a vehicle. - /// The value changes over time. - /// - /// vehicle id - /// a number between 0 and 99 - uint GetTimedVehicleRand(ushort vehicleId); - - /// - /// Calculates the randomization value for a vehicle. - /// The value is static throughout the vehicle's lifetime. - /// - /// vehicle id - /// a number between 0 and 99 - uint GetStaticVehicleRand(ushort vehicleId); - - /// - /// Logs the given vehicle for traffic measurement. - /// - /// vehicle id - /// vehicle - void LogTraffic(ushort vehicleId, ref Vehicle vehicle); - - /// - /// Randomly selects a new set of DLS parameters. - /// - /// ext. vehicle data - void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle); - } -} + using API.Traffic.Data; + using API.Traffic.Enums; + + public interface IExtVehicleManager { + /// + /// Extended vehicle data + /// + ExtVehicle[] ExtVehicles { get; } + + /// + /// Handles a released vehicle. + /// + /// vehicle id + /// vehicle data + void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a released vehicle. + /// + /// vehicle + void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData); + + /// + /// Handles a created vehicle. + /// + /// vehicle id + /// vehicle data + void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a created vehicle. + /// + /// vehicle + void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData); + + /// + /// Handles a spawned vehicle. + /// + /// vehicle id + /// vehicle data + void OnSpawnVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a spawned vehicle. + /// + /// vehicle + void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData); + + /// + /// Handles a despawned vehicle. + /// + /// vehicle id + /// vehicle data + void OnDespawnVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a despawned vehicle. + /// + /// vehicle + void OnDespawn(ref ExtVehicle extVehicle); + + /// + /// Handles a vehicle when path-finding starts. + /// + /// vehicle id + /// vehicle data + /// vehicle type to set, optional + /// vehicle type + ExtVehicleType OnStartPathFind(ushort vehicleId, ref Vehicle vehicleData, ExtVehicleType? vehicleType); + + /// + /// Handles a vehicle when path-finding starts. + /// + /// vehicle + /// vehicle data + /// vehicle type to set, optional + /// vehicle type + ExtVehicleType OnStartPathFind(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ExtVehicleType? vehicleType); + + /// + /// Retrieves the driver citizen instance id for the given vehicle. + /// + /// vehicle id + /// vehicle data + /// + ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data); + + /// + /// Updates the vehicle's current path position. + /// + /// vehicle id + /// vehicle data + void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Updates the vehicle's current path position. + /// + /// vehicle + /// vehicle data + /// ext. segment end + /// current path position + /// next path position + void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos); + + /// + /// Unlinks the given vehicle from any segment end that the vehicle is currently linked to. + /// + /// vehicle + void Unlink(ref ExtVehicle extVehicle); + + /// + /// Updates the vehicle's junction transit state to the given value. + /// + /// vehicle + /// junction transit state + void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState); + + /// + /// Determines if the junction transit state has been recently modified + /// + /// /// vehicle + /// true if the junction transit state is considered new, false otherwise + bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle); + + /// + /// Triggers the randomization mechanism that might select a new random value for time-varying vehicle behavior (e.g. speed) + /// + /// ext. vehicle data + /// whether to force generation of a new value + void StepRand(ref ExtVehicle extVehicle, bool force); + + /// + /// Calculates the current randomization value for a vehicle. + /// The value changes over time. + /// + /// vehicle id + /// a number between 0 and 99 + uint GetTimedVehicleRand(ushort vehicleId); + + /// + /// Calculates the randomization value for a vehicle. + /// The value is static throughout the vehicle's lifetime. + /// + /// vehicle id + /// a number between 0 and 99 + uint GetStaticVehicleRand(ushort vehicleId); + + /// + /// Logs the given vehicle for traffic measurement. + /// + /// vehicle id + /// vehicle + void LogTraffic(ushort vehicleId, ref Vehicle vehicle); + + /// + /// Randomly selects a new set of DLS parameters. + /// + /// ext. vehicle data + void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Manager/IManagerFactory.cs b/TLM/TMPE.API/Manager/IManagerFactory.cs index 7bc7b5d3f..a9818415c 100644 --- a/TLM/TMPE.API/Manager/IManagerFactory.cs +++ b/TLM/TMPE.API/Manager/IManagerFactory.cs @@ -5,6 +5,8 @@ using TrafficManager.Manager; namespace TrafficManager.Manager { + using API.Manager; + public interface IManagerFactory { IAdvancedParkingManager AdvancedParkingManager { get; } ICustomSegmentLightsManager CustomSegmentLightsManager { get; } diff --git a/TLM/TMPE.API/Manager/ITrafficLightManager.cs b/TLM/TMPE.API/Manager/ITrafficLightManager.cs index 17f51102a..8caaf8676 100644 --- a/TLM/TMPE.API/Manager/ITrafficLightManager.cs +++ b/TLM/TMPE.API/Manager/ITrafficLightManager.cs @@ -5,6 +5,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Manager { + using API.Traffic.Enums; + public interface ITrafficLightManager { // TODO documentation bool AddTrafficLight(ushort nodeId, ref NetNode node); diff --git a/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs b/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs index dcf7119cb..a96264966 100644 --- a/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs +++ b/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs @@ -7,6 +7,8 @@ using static TrafficManager.Traffic.Data.PrioritySegment; namespace TrafficManager.Manager { + using API.Traffic.Enums; + public interface ITrafficPriorityManager { // TODO define me! diff --git a/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs b/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs index 42aa1ec67..6b77b6f0c 100644 --- a/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs +++ b/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs @@ -7,6 +7,8 @@ using UnityEngine; namespace TrafficManager.Manager { + using API.Traffic.Data; + public interface IVehicleBehaviorManager { // TODO define me! // TODO documentation diff --git a/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs b/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs index fd75364b0..abbcbcc3d 100644 --- a/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs +++ b/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs @@ -1,47 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +namespace TrafficManager.API.Manager { + using System.Collections.Generic; + using Traffic.Enums; + using TrafficManager.Traffic.Enums; -namespace TrafficManager.Manager { - public interface IVehicleRestrictionsManager { - // TODO documentation - void AddAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); - ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); - ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); - IDictionary GetAllowedVehicleTypesAsDict(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); - HashSet GetAllowedVehicleTypesAsSet(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); - ExtVehicleType GetBaseMask(uint laneId, VehicleRestrictionsMode includeBusLanes); - ExtVehicleType GetBaseMask(NetInfo.Lane laneInfo, VehicleRestrictionsMode includeBusLanes); - ExtVehicleType GetDefaultAllowedVehicleTypes(NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); - ExtVehicleType GetDefaultAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); - bool IsAllowed(ExtVehicleType? allowedTypes, ExtVehicleType vehicleType); - bool IsBicycleAllowed(ExtVehicleType? allowedTypes); - bool IsBlimpAllowed(ExtVehicleType? allowedTypes); - bool IsBusAllowed(ExtVehicleType? allowedTypes); - bool IsCableCarAllowed(ExtVehicleType? allowedTypes); - bool IsCargoTrainAllowed(ExtVehicleType? allowedTypes); - bool IsCargoTruckAllowed(ExtVehicleType? allowedTypes); - bool IsEmergencyAllowed(ExtVehicleType? allowedTypes); - bool IsFerryAllowed(ExtVehicleType? allowedTypes); - bool IsMonorailSegment(NetInfo segmentInfo); - bool IsPassengerCarAllowed(ExtVehicleType? allowedTypes); - bool IsPassengerTrainAllowed(ExtVehicleType? allowedTypes); - bool IsRailLane(NetInfo.Lane laneInfo); - bool IsRailSegment(NetInfo segmentInfo); - bool IsRailVehicleAllowed(ExtVehicleType? allowedTypes); - bool IsRoadLane(NetInfo.Lane laneInfo); - bool IsRoadSegment(NetInfo segmentInfo); - bool IsRoadVehicleAllowed(ExtVehicleType? allowedTypes); - bool IsServiceAllowed(ExtVehicleType? allowedTypes); - bool IsTaxiAllowed(ExtVehicleType? allowedTypes); - bool IsTramAllowed(ExtVehicleType? allowedTypes); - bool IsTramLane(NetInfo.Lane laneInfo); - void NotifyStartEndNode(ushort segmentId); - void OnLevelUnloading(); - void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); - void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add); - } -} + public interface IVehicleRestrictionsManager { + // TODO documentation + void AddAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); + ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); + ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); + IDictionary GetAllowedVehicleTypesAsDict(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); + HashSet GetAllowedVehicleTypesAsSet(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); + ExtVehicleType GetBaseMask(uint laneId, VehicleRestrictionsMode includeBusLanes); + ExtVehicleType GetBaseMask(NetInfo.Lane laneInfo, VehicleRestrictionsMode includeBusLanes); + ExtVehicleType GetDefaultAllowedVehicleTypes(NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); + ExtVehicleType GetDefaultAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); + bool IsAllowed(ExtVehicleType? allowedTypes, ExtVehicleType vehicleType); + bool IsBicycleAllowed(ExtVehicleType? allowedTypes); + bool IsBlimpAllowed(ExtVehicleType? allowedTypes); + bool IsBusAllowed(ExtVehicleType? allowedTypes); + bool IsCableCarAllowed(ExtVehicleType? allowedTypes); + bool IsCargoTrainAllowed(ExtVehicleType? allowedTypes); + bool IsCargoTruckAllowed(ExtVehicleType? allowedTypes); + bool IsEmergencyAllowed(ExtVehicleType? allowedTypes); + bool IsFerryAllowed(ExtVehicleType? allowedTypes); + bool IsMonorailSegment(NetInfo segmentInfo); + bool IsPassengerCarAllowed(ExtVehicleType? allowedTypes); + bool IsPassengerTrainAllowed(ExtVehicleType? allowedTypes); + bool IsRailLane(NetInfo.Lane laneInfo); + bool IsRailSegment(NetInfo segmentInfo); + bool IsRailVehicleAllowed(ExtVehicleType? allowedTypes); + bool IsRoadLane(NetInfo.Lane laneInfo); + bool IsRoadSegment(NetInfo segmentInfo); + bool IsRoadVehicleAllowed(ExtVehicleType? allowedTypes); + bool IsServiceAllowed(ExtVehicleType? allowedTypes); + bool IsTaxiAllowed(ExtVehicleType? allowedTypes); + bool IsTramAllowed(ExtVehicleType? allowedTypes); + bool IsTramLane(NetInfo.Lane laneInfo); + void NotifyStartEndNode(ushort segmentId); + void OnLevelUnloading(); + void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); + void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/TMPE.API.csproj b/TLM/TMPE.API/TMPE.API.csproj index ee4a1aa2b..0e17acb7a 100644 --- a/TLM/TMPE.API/TMPE.API.csproj +++ b/TLM/TMPE.API/TMPE.API.csproj @@ -7,7 +7,7 @@ {C911D31C-C85D-42A8-A839-7B209A715495} Library Properties - TrafficManager + TrafficManager.API TMPE.API v3.5 512 diff --git a/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs b/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs index a7628202e..6872a90d7 100644 --- a/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs +++ b/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs @@ -6,6 +6,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; + public struct ExtCitizen { public uint citizenId; diff --git a/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs b/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs index 90dbcfdbc..37a683d03 100644 --- a/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs +++ b/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs @@ -8,6 +8,8 @@ using UnityEngine; namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; + public struct ExtCitizenInstance { public ushort instanceId; diff --git a/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs b/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs index 177198fe7..58de23a39 100644 --- a/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs +++ b/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs @@ -1,101 +1,100 @@ -using TrafficManager.Traffic.Enums; -using UnityEngine; +namespace TrafficManager.API.Traffic.Data { + using Enums; -namespace TrafficManager.Traffic.Data { - public struct ExtVehicle { - public ushort vehicleId; - public uint lastPathId; - public byte lastPathPositionIndex; - public uint lastTransitStateUpdate; - public uint lastPositionUpdate; - public float totalLength; - public int waitTime; - public ExtVehicleFlags flags; - public ExtVehicleType vehicleType; - public bool heavyVehicle; - public bool recklessDriver; - public ushort currentSegmentId; - public bool currentStartNode; - public byte currentLaneIndex; - public ushort nextSegmentId; - public byte nextLaneIndex; - public ushort previousVehicleIdOnSegment; - public ushort nextVehicleIdOnSegment; - public ushort lastAltLaneSelSegmentId; - public byte timedRand; - public VehicleJunctionTransitState junctionTransitState; - // Dynamic Lane Selection - public bool dlsReady; - public float maxReservedSpace; - public float laneSpeedRandInterval; - public int maxOptLaneChanges; - public float maxUnsafeSpeedDiff; - public float minSafeSpeedImprovement; - public float minSafeTrafficImprovement; + public struct ExtVehicle { + public ushort vehicleId; + public uint lastPathId; + public byte lastPathPositionIndex; + public uint lastTransitStateUpdate; + public uint lastPositionUpdate; + public float totalLength; + public int waitTime; + public ExtVehicleFlags flags; + public ExtVehicleType vehicleType; + public bool heavyVehicle; + public bool recklessDriver; + public ushort currentSegmentId; + public bool currentStartNode; + public byte currentLaneIndex; + public ushort nextSegmentId; + public byte nextLaneIndex; + public ushort previousVehicleIdOnSegment; + public ushort nextVehicleIdOnSegment; + public ushort lastAltLaneSelSegmentId; + public byte timedRand; + public VehicleJunctionTransitState junctionTransitState; + // Dynamic Lane Selection + public bool dlsReady; + public float maxReservedSpace; + public float laneSpeedRandInterval; + public int maxOptLaneChanges; + public float maxUnsafeSpeedDiff; + public float minSafeSpeedImprovement; + public float minSafeTrafficImprovement; - public override string ToString() { - return $"[VehicleState\n" + - "\t" + $"vehicleId = {vehicleId}\n" + - "\t" + $"lastPathId = {lastPathId}\n" + - "\t" + $"lastPathPositionIndex = {lastPathPositionIndex}\n" + - "\t" + $"junctionTransitState = {junctionTransitState}\n" + - "\t" + $"lastTransitStateUpdate = {lastTransitStateUpdate}\n" + - "\t" + $"lastPositionUpdate = {lastPositionUpdate}\n" + - "\t" + $"totalLength = {totalLength}\n" + - "\t" + $"waitTime = {waitTime}\n" + - "\t" + $"flags = {flags}\n" + - "\t" + $"vehicleType = {vehicleType}\n" + - "\t" + $"heavyVehicle = {heavyVehicle}\n" + - "\t" + $"recklessDriver = {recklessDriver}\n" + - "\t" + $"currentSegmentId = {currentSegmentId}\n" + - "\t" + $"currentStartNode = {currentStartNode}\n" + - "\t" + $"currentLaneIndex = {currentLaneIndex}\n" + - "\t" + $"nextSegmentId = {nextSegmentId}\n" + - "\t" + $"nextLaneIndex = {nextLaneIndex}\n" + - "\t" + $"previousVehicleIdOnSegment = {previousVehicleIdOnSegment}\n" + - "\t" + $"nextVehicleIdOnSegment = {nextVehicleIdOnSegment}\n" + - "\t" + $"lastAltLaneSelSegmentId = {lastAltLaneSelSegmentId}\n" + - "\t" + $"junctionTransitState = {junctionTransitState}\n" + - "\t" + $"timedRand = {timedRand}\n" + - "\t" + $"dlsReady = {dlsReady}\n" + - "\t" + $"maxReservedSpace = {maxReservedSpace}\n" + - "\t" + $"laneSpeedRandInterval = {laneSpeedRandInterval}\n" + - "\t" + $"maxOptLaneChanges = {maxOptLaneChanges}\n" + - "\t" + $"maxUnsafeSpeedDiff = {maxUnsafeSpeedDiff}\n" + - "\t" + $"minSafeSpeedImprovement = {minSafeSpeedImprovement}\n" + - "\t" + $"minSafeTrafficImprovement = {minSafeTrafficImprovement}\n" + - "VehicleState]"; - } + public override string ToString() { + return $"[VehicleState\n" + + "\t" + $"vehicleId = {vehicleId}\n" + + "\t" + $"lastPathId = {lastPathId}\n" + + "\t" + $"lastPathPositionIndex = {lastPathPositionIndex}\n" + + "\t" + $"junctionTransitState = {junctionTransitState}\n" + + "\t" + $"lastTransitStateUpdate = {lastTransitStateUpdate}\n" + + "\t" + $"lastPositionUpdate = {lastPositionUpdate}\n" + + "\t" + $"totalLength = {totalLength}\n" + + "\t" + $"waitTime = {waitTime}\n" + + "\t" + $"flags = {flags}\n" + + "\t" + $"vehicleType = {vehicleType}\n" + + "\t" + $"heavyVehicle = {heavyVehicle}\n" + + "\t" + $"recklessDriver = {recklessDriver}\n" + + "\t" + $"currentSegmentId = {currentSegmentId}\n" + + "\t" + $"currentStartNode = {currentStartNode}\n" + + "\t" + $"currentLaneIndex = {currentLaneIndex}\n" + + "\t" + $"nextSegmentId = {nextSegmentId}\n" + + "\t" + $"nextLaneIndex = {nextLaneIndex}\n" + + "\t" + $"previousVehicleIdOnSegment = {previousVehicleIdOnSegment}\n" + + "\t" + $"nextVehicleIdOnSegment = {nextVehicleIdOnSegment}\n" + + "\t" + $"lastAltLaneSelSegmentId = {lastAltLaneSelSegmentId}\n" + + "\t" + $"junctionTransitState = {junctionTransitState}\n" + + "\t" + $"timedRand = {timedRand}\n" + + "\t" + $"dlsReady = {dlsReady}\n" + + "\t" + $"maxReservedSpace = {maxReservedSpace}\n" + + "\t" + $"laneSpeedRandInterval = {laneSpeedRandInterval}\n" + + "\t" + $"maxOptLaneChanges = {maxOptLaneChanges}\n" + + "\t" + $"maxUnsafeSpeedDiff = {maxUnsafeSpeedDiff}\n" + + "\t" + $"minSafeSpeedImprovement = {minSafeSpeedImprovement}\n" + + "\t" + $"minSafeTrafficImprovement = {minSafeTrafficImprovement}\n" + + "VehicleState]"; + } - public ExtVehicle(ushort vehicleId) { - this.vehicleId = vehicleId; - lastPathId = 0; - lastPathPositionIndex = 0; - lastTransitStateUpdate = 0; - lastPositionUpdate = 0; - totalLength = 0; - waitTime = 0; - flags = ExtVehicleFlags.None; - vehicleType = ExtVehicleType.None; - heavyVehicle = false; - recklessDriver = false; - currentSegmentId = 0; - currentStartNode = false; - currentLaneIndex = 0; - nextSegmentId = 0; - nextLaneIndex = 0; - previousVehicleIdOnSegment = 0; - nextVehicleIdOnSegment = 0; - lastAltLaneSelSegmentId = 0; - junctionTransitState = VehicleJunctionTransitState.None; - timedRand = 0; - dlsReady = false; - maxReservedSpace = 0; - laneSpeedRandInterval = 0; - maxOptLaneChanges = 0; - maxUnsafeSpeedDiff = 0; - minSafeSpeedImprovement = 0; - minSafeTrafficImprovement = 0; - } - } -} + public ExtVehicle(ushort vehicleId) { + this.vehicleId = vehicleId; + lastPathId = 0; + lastPathPositionIndex = 0; + lastTransitStateUpdate = 0; + lastPositionUpdate = 0; + totalLength = 0; + waitTime = 0; + flags = ExtVehicleFlags.None; + vehicleType = ExtVehicleType.None; + heavyVehicle = false; + recklessDriver = false; + currentSegmentId = 0; + currentStartNode = false; + currentLaneIndex = 0; + nextSegmentId = 0; + nextLaneIndex = 0; + previousVehicleIdOnSegment = 0; + nextVehicleIdOnSegment = 0; + lastAltLaneSelSegmentId = 0; + junctionTransitState = VehicleJunctionTransitState.None; + timedRand = 0; + dlsReady = false; + maxReservedSpace = 0; + laneSpeedRandInterval = 0; + maxOptLaneChanges = 0; + maxUnsafeSpeedDiff = 0; + minSafeSpeedImprovement = 0; + minSafeTrafficImprovement = 0; + } + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs b/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs index 5d52eabf0..dda2fd986 100644 --- a/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs +++ b/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs @@ -1,114 +1,110 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic.Enums; - -namespace TrafficManager.Traffic.Data { - public struct PathCreationArgs { - /// - /// Extended path type - /// - public ExtPathType extPathType; - - /// - /// Extended vehicle type - /// - public ExtVehicleType extVehicleType; - - /// - /// (optional) vehicle id - /// - public ushort vehicleId; - - /// - /// is entity alredy spawned? - /// - public bool spawned; - - /// - /// Current build index - /// - public uint buildIndex; - - /// - /// Start position (first alternative) - /// - public PathUnit.Position startPosA; - - /// - /// Start position (second alternative, opposite road side) - /// - public PathUnit.Position startPosB; - - /// - /// End position (first alternative) - /// - public PathUnit.Position endPosA; - - /// - /// End position (second alternative, opposite road side) - /// - public PathUnit.Position endPosB; - - /// - /// (optional) position of the parked vehicle - /// - public PathUnit.Position vehiclePosition; - - /// - /// Allowed set of lane types - /// - public NetInfo.LaneType laneTypes; - - /// - /// Allowed set of vehicle types - /// - public VehicleInfo.VehicleType vehicleTypes; - - /// - /// Maximum allowed path length - /// - public float maxLength; - - /// - /// Is the path calculated for a heavy vehicle? - /// - public bool isHeavyVehicle; - - /// - /// Is the path calculated for a vehicle with a combustion engine? - /// - public bool hasCombustionEngine; - - /// - /// Should blocked segments be ignored? - /// - public bool ignoreBlocked; - - /// - /// Should flooded segments be ignored? - /// - public bool ignoreFlooded; - - /// - /// Should path costs be ignored? - /// - public bool ignoreCosts; - - /// - /// Should random parking apply? - /// - public bool randomParking; - - /// - /// Should the path be stable (and not randomized)? - /// - public bool stablePath; - - /// - /// Is this a high priority path? - /// - public bool skipQueue; - } -} +namespace TrafficManager.API.Traffic.Data { + using Enums; + + public struct PathCreationArgs { + /// + /// Extended path type + /// + public ExtPathType extPathType; + + /// + /// Extended vehicle type + /// + public ExtVehicleType extVehicleType; + + /// + /// (optional) vehicle id + /// + public ushort vehicleId; + + /// + /// is entity alredy spawned? + /// + public bool spawned; + + /// + /// Current build index + /// + public uint buildIndex; + + /// + /// Start position (first alternative) + /// + public PathUnit.Position startPosA; + + /// + /// Start position (second alternative, opposite road side) + /// + public PathUnit.Position startPosB; + + /// + /// End position (first alternative) + /// + public PathUnit.Position endPosA; + + /// + /// End position (second alternative, opposite road side) + /// + public PathUnit.Position endPosB; + + /// + /// (optional) position of the parked vehicle + /// + public PathUnit.Position vehiclePosition; + + /// + /// Allowed set of lane types + /// + public NetInfo.LaneType laneTypes; + + /// + /// Allowed set of vehicle types + /// + public VehicleInfo.VehicleType vehicleTypes; + + /// + /// Maximum allowed path length + /// + public float maxLength; + + /// + /// Is the path calculated for a heavy vehicle? + /// + public bool isHeavyVehicle; + + /// + /// Is the path calculated for a vehicle with a combustion engine? + /// + public bool hasCombustionEngine; + + /// + /// Should blocked segments be ignored? + /// + public bool ignoreBlocked; + + /// + /// Should flooded segments be ignored? + /// + public bool ignoreFlooded; + + /// + /// Should path costs be ignored? + /// + public bool ignoreCosts; + + /// + /// Should random parking apply? + /// + public bool randomParking; + + /// + /// Should the path be stable (and not randomized)? + /// + public bool stablePath; + + /// + /// Is this a high priority path? + /// + public bool skipQueue; + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs b/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs index eb0f2bb92..94f2d2045 100644 --- a/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs +++ b/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic.Enums; +namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; -namespace TrafficManager.Traffic.Data { public struct PathUnitQueueItem { public uint nextPathUnitId; // access requires acquisition of CustomPathFind.QueueLock public ExtVehicleType vehicleType; // access requires acquisition of m_bufferLock diff --git a/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs b/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs index 90b7d82cc..db382db15 100644 --- a/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs +++ b/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs @@ -2,6 +2,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; + /// /// A priority segment specifies the priority signs that are present at each end of a certain segment. /// diff --git a/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs b/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs index 246847c1a..2d14ca4b3 100644 --- a/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs +++ b/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - /// - /// Indicates if a private car [may]/[shall]/[must not] be used - /// - public enum CarUsagePolicy { - /// - /// Citizens may use their own car - /// - Allowed, - /// - /// Citizens are forced to use their parked car - /// - ForcedParked, - /// - /// Citizens are forced to use a pocket car - /// - ForcedPocket, - /// - /// Citizens are forbidden to use their car - /// - Forbidden - } -} +namespace TrafficManager.API.Traffic.Enums { + /// + /// Indicates if a private car [may]/[shall]/[must not] be used + /// + public enum CarUsagePolicy { + /// + /// Citizens may use their own car + /// + Allowed, + /// + /// Citizens are forced to use their parked car + /// + ForcedParked, + /// + /// Citizens are forced to use a pocket car + /// + ForcedPocket, + /// + /// Citizens are forbidden to use their car + /// + Forbidden + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs b/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs index 4c6137001..4b92a00ae 100644 --- a/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs +++ b/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { +namespace TrafficManager.API.Traffic.Enums { public enum EmergencyBehavior { /// /// No custom behavior diff --git a/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs b/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs index efe8cbcc8..a798b9a4c 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs @@ -1,21 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum ExtParkingSpaceLocation { - /// - /// No parking space location - /// - None = 0, - /// - /// Road-side parking space - /// - RoadSide = 1, - /// - /// Building parking space - /// - Building = 2 - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum ExtParkingSpaceLocation { + /// + /// No parking space location + /// + None = 0, + /// + /// Road-side parking space + /// + RoadSide = 1, + /// + /// Building parking space + /// + Building = 2 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs b/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs index 536f4116a..12b7e0b36 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs @@ -1,84 +1,79 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum ExtPathMode { - None = 0, - /// - /// Indicates that the citizen requires a walking path to their parked car - /// - RequiresWalkingPathToParkedCar = 1, - /// - /// Indicates that a walking path to the parked car is being calculated - /// - CalculatingWalkingPathToParkedCar = 2, - /// - /// Indicates that the citizen is walking to their parked car - /// - WalkingToParkedCar = 3, - /// - /// Indicates that the citizen is close to their parked car - /// - ApproachingParkedCar = 4, - /// - /// Indicates that the citizen has reached their parked car and requires a car path now - /// - RequiresCarPath = 5, - /// - /// Indicates that a direct car path to the target is being calculated - /// - CalculatingCarPathToTarget = 6, - /// - /// Indicates that a car path to a known parking spot near the target is being calculated - /// - CalculatingCarPathToKnownParkPos = 7, - /// - /// Indicates that the citizen is currently driving on a direct path to target - /// - DrivingToTarget = 8, - /// - /// Indiciates that the citizen is currently driving to a known parking spot near the target - /// - DrivingToKnownParkPos = 9, - /// - /// Indicates that the vehicle is being parked on an alternative parking position - /// - RequiresWalkingPathToTarget = 10, - /// - /// Indicates that parking has failed - /// - ParkingFailed = 11, - /// - /// Indicates that a path to an alternative parking position is being calculated - /// - CalculatingCarPathToAltParkPos = 12, - /// - /// Indicates that the vehicle is on a path to an alternative parking position - /// - DrivingToAltParkPos = 13, - /// - /// Indicates that a walking path to target is being calculated - /// - CalculatingWalkingPathToTarget = 14, - /// - /// Indicates that the citizen is currently walking to the target - /// - WalkingToTarget = 15, - /// - /// (DEPRECATED) Indicates that the citizen is using public transport (bus/train/tram/subway) to reach the target - /// - __Deprecated__PublicTransportToTarget = 16, - /// - /// Indicates that the citizen is using a taxi to reach the target - /// - TaxiToTarget = 17, - /// - /// Indicates that the driving citizen requires a direct path to target (driving/public transport) - /// where possible transitions between different modes of transport happen as required (thus no search - /// for parking spaces is performed beforehand) - /// - RequiresMixedCarPathToTarget = 18, - } -} +namespace TrafficManager.Traffic.Enums { + public enum ExtPathMode { + None = 0, + /// + /// Indicates that the citizen requires a walking path to their parked car + /// + RequiresWalkingPathToParkedCar = 1, + /// + /// Indicates that a walking path to the parked car is being calculated + /// + CalculatingWalkingPathToParkedCar = 2, + /// + /// Indicates that the citizen is walking to their parked car + /// + WalkingToParkedCar = 3, + /// + /// Indicates that the citizen is close to their parked car + /// + ApproachingParkedCar = 4, + /// + /// Indicates that the citizen has reached their parked car and requires a car path now + /// + RequiresCarPath = 5, + /// + /// Indicates that a direct car path to the target is being calculated + /// + CalculatingCarPathToTarget = 6, + /// + /// Indicates that a car path to a known parking spot near the target is being calculated + /// + CalculatingCarPathToKnownParkPos = 7, + /// + /// Indicates that the citizen is currently driving on a direct path to target + /// + DrivingToTarget = 8, + /// + /// Indiciates that the citizen is currently driving to a known parking spot near the target + /// + DrivingToKnownParkPos = 9, + /// + /// Indicates that the vehicle is being parked on an alternative parking position + /// + RequiresWalkingPathToTarget = 10, + /// + /// Indicates that parking has failed + /// + ParkingFailed = 11, + /// + /// Indicates that a path to an alternative parking position is being calculated + /// + CalculatingCarPathToAltParkPos = 12, + /// + /// Indicates that the vehicle is on a path to an alternative parking position + /// + DrivingToAltParkPos = 13, + /// + /// Indicates that a walking path to target is being calculated + /// + CalculatingWalkingPathToTarget = 14, + /// + /// Indicates that the citizen is currently walking to the target + /// + WalkingToTarget = 15, + /// + /// (DEPRECATED) Indicates that the citizen is using public transport (bus/train/tram/subway) to reach the target + /// + __Deprecated__PublicTransportToTarget = 16, + /// + /// Indicates that the citizen is using a taxi to reach the target + /// + TaxiToTarget = 17, + /// + /// Indicates that the driving citizen requires a direct path to target (driving/public transport) + /// where possible transitions between different modes of transport happen as required (thus no search + /// for parking spaces is performed beforehand) + /// + RequiresMixedCarPathToTarget = 18, + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs b/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs index c00774acf..8786a6aeb 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs @@ -1,25 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum ExtPathState { - /// - /// No path - /// - None = 0, - /// - /// Path is currently being calculated - /// - Calculating = 1, - /// - /// Path-finding has succeeded - /// - Ready = 2, - /// - /// Path-finding has failed - /// - Failed = 3 - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum ExtPathState { + /// + /// No path + /// + None = 0, + /// + /// Path is currently being calculated + /// + Calculating = 1, + /// + /// Path-finding has succeeded + /// + Ready = 2, + /// + /// Path-finding has failed + /// + Failed = 3 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs b/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs index 6afc844be..9e91b65d9 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { +namespace TrafficManager.API.Traffic.Enums { public enum ExtPathType { /// /// Mixed path diff --git a/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs b/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs index 353304b6d..27fa5be0d 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs @@ -1,33 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum ExtSoftPathState { - /// - /// No path - /// - None = 0, - /// - /// Path is currently being calculated - /// - Calculating = 1, - /// - /// Path-finding has succeeded and must be handled appropriately - /// - Ready = 2, - /// - /// Path-finding has failed and must be handled appropriately - /// - FailedHard = 3, - /// - /// Path-finding must be retried (soft path-find failure) - /// - FailedSoft = 4, - /// - /// Path-finding result must not be handled by the citizen because the path will be transferred to a vehicle - /// - Ignore = 5 - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum ExtSoftPathState { + /// + /// No path + /// + None = 0, + /// + /// Path is currently being calculated + /// + Calculating = 1, + /// + /// Path-finding has succeeded and must be handled appropriately + /// + Ready = 2, + /// + /// Path-finding has failed and must be handled appropriately + /// + FailedHard = 3, + /// + /// Path-finding must be retried (soft path-find failure) + /// + FailedSoft = 4, + /// + /// Path-finding result must not be handled by the citizen because the path will be transferred to a vehicle + /// + Ignore = 5 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs b/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs index adee440fa..eee7b7db8 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs @@ -1,22 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; -namespace TrafficManager.Traffic.Enums { - [Flags] - public enum ExtTransportMode { - /// - /// No information about which mode of transport is used - /// - None = 0, - /// - /// Travelling by car - /// - Car = 1, - /// - /// Travelling by means of public transport - /// - PublicTransport = 2 - } -} + [Flags] + public enum ExtTransportMode { + /// + /// No information about which mode of transport is used + /// + None = 0, + /// + /// Travelling by car + /// + Car = 1, + /// + /// Travelling by means of public transport + /// + PublicTransport = 2 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs b/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs index 12e9b9a06..d9fdf2cda 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; -namespace TrafficManager.Traffic.Enums { - // TODO why do we need this? - [Flags] - public enum ExtVehicleFlags { - None = 0, - Created = 1, - Spawned = 1 << 1 - } -} + // TODO why do we need this? + [Flags] + public enum ExtVehicleFlags { + None = 0, + Created = 1, + Spawned = 1 << 1 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs b/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs index 51e9116a8..ca7ef70cd 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs @@ -1,40 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; -namespace TrafficManager.Traffic { - // TODO this enum should be moved to package TrafficManager.Traffic.Enums but deserialization fails if we just do that now. - [Flags] - public enum ExtVehicleType { - None = 0, - PassengerCar = 1, - Bus = 1 << 1, - Taxi = 1 << 2, - CargoTruck = 1 << 3, - Service = 1 << 4, - Emergency = 1 << 5, - PassengerTrain = 1 << 6, - CargoTrain = 1 << 7, - Tram = 1 << 8, - Bicycle = 1 << 9, - Pedestrian = 1 << 10, - PassengerShip = 1 << 11, - CargoShip = 1 << 12, - PassengerPlane = 1 << 13, - Helicopter = 1 << 14, - CableCar = 1 << 15, - PassengerFerry = 1 << 16, - PassengerBlimp = 1 << 17, - CargoPlane = 1 << 18, - Plane = PassengerPlane | CargoPlane, - Ship = PassengerShip | CargoShip, - CargoVehicle = CargoTruck | CargoTrain | CargoShip | CargoPlane, - PublicTransport = Bus | Taxi | Tram | PassengerTrain, - RoadPublicTransport = Bus | Taxi, - RoadVehicle = PassengerCar | Bus | Taxi | CargoTruck | Service | Emergency, - RailVehicle = PassengerTrain | CargoTrain, - NonTransportRoadVehicle = RoadVehicle & ~PublicTransport, - Ferry = PassengerFerry, - Blimp = PassengerBlimp - } -} + [Flags] + public enum ExtVehicleType { + None = 0, + PassengerCar = 1, + Bus = 1 << 1, + Taxi = 1 << 2, + CargoTruck = 1 << 3, + Service = 1 << 4, + Emergency = 1 << 5, + PassengerTrain = 1 << 6, + CargoTrain = 1 << 7, + Tram = 1 << 8, + Bicycle = 1 << 9, + Pedestrian = 1 << 10, + PassengerShip = 1 << 11, + CargoShip = 1 << 12, + PassengerPlane = 1 << 13, + Helicopter = 1 << 14, + CableCar = 1 << 15, + PassengerFerry = 1 << 16, + PassengerBlimp = 1 << 17, + CargoPlane = 1 << 18, + Plane = PassengerPlane | CargoPlane, + Ship = PassengerShip | CargoShip, + CargoVehicle = CargoTruck | CargoTrain | CargoShip | CargoPlane, + PublicTransport = Bus | Taxi | Tram | PassengerTrain, + RoadPublicTransport = Bus | Taxi, + RoadVehicle = PassengerCar | Bus | Taxi | CargoTruck | Service | Emergency, + RailVehicle = PassengerTrain | CargoTrain, + NonTransportRoadVehicle = RoadVehicle & ~PublicTransport, + Ferry = PassengerFerry, + Blimp = PassengerBlimp + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs b/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs index c66fd53f2..b7b06c312 100644 --- a/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs @@ -1,18 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.TrafficLight { - // TODO this enum should be moved to TrafficManager.Traffic.Enums but deserialization fails if we just do that now. - public enum FlowWaitCalcMode { - /// - /// traffic measurements are averaged - /// - Mean, - /// - /// traffic measurements are summed up - /// - Total - } -} +namespace TrafficManager.API.Traffic.Enums { + // TODO this enum should be moved to TrafficManager.Traffic.Enums but deserialization fails if we just do that now. + public enum FlowWaitCalcMode { + /// + /// traffic measurements are averaged + /// + Mean, + /// + /// traffic measurements are summed up + /// + Total + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs b/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs index 9536899fc..9f364adb1 100644 --- a/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum GeometryCalculationMode { - Init, - Propagate, - NoPropagate - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum GeometryCalculationMode { + Init, + Propagate, + NoPropagate + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs b/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs index 73b7e4f6c..5e7120b19 100644 --- a/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs +++ b/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs @@ -1,18 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; + using JetBrains.Annotations; -namespace TrafficManager.Traffic.Enums { - [Flags] - public enum LaneArrows { // compatible with NetLane.Flags - None = 0, - Forward = 16, - Left = 32, - Right = 64, - LeftForward = 48, - LeftRight = 96, - ForwardRight = 80, - LeftForwardRight = 112 - } -} + [Flags] + public enum LaneArrows { + // compatible with NetLane.Flags + None = 0, + Forward = 16, + Left = 32, + Right = 64, + + [UsedImplicitly] + LeftForward = Left + Forward, + + [UsedImplicitly] + LeftRight = Left + Right, + + [UsedImplicitly] + ForwardRight = Forward + Right, + LeftForwardRight = Left + Forward + Right + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs b/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs index 96b5492e7..c82940b14 100644 --- a/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs +++ b/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs @@ -1,25 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum LaneEndTransitionType { - /// - /// No connection - /// - Invalid, - /// - /// Lane arrow or regular lane connection - /// - Default, - /// - /// Custom lane connection - /// - LaneConnection, - /// - /// Relaxed connection for road vehicles [!] that do not have to follow lane arrows - /// - Relaxed - } -} +namespace TrafficManager.Traffic.Enums { + public enum LaneEndTransitionType { + /// + /// No connection + /// + Invalid, + /// + /// Lane arrow or regular lane connection + /// + Default, + /// + /// Custom lane connection + /// + LaneConnection, + /// + /// Relaxed connection for road vehicles [!] that do not have to follow lane arrows + /// + Relaxed + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/LightMode.cs b/TLM/TMPE.API/Traffic/Enums/LightMode.cs index 1e8b9eef4..77c28d400 100644 --- a/TLM/TMPE.API/Traffic/Enums/LightMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/LightMode.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { +namespace TrafficManager.API.Traffic.Enums { public enum LightMode { Simple = 1, // <^> SingleLeft = 2, // <, ^> diff --git a/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs b/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs index 904f684d7..8bad7cdde 100644 --- a/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs +++ b/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - /// - /// Indicates the current state while approaching a private car - /// - public enum ParkedCarApproachState { - /// - /// Citizen is not approaching their parked car - /// - None, - /// - /// Citizen is currently approaching their parked car - /// - Approaching, - /// - /// Citizen has approaching their parked car - /// - Approached, - /// - /// Citizen failed to approach their parked car - /// - Failure - } -} +namespace TrafficManager.API.Traffic.Enums { + /// + /// Indicates the current state while approaching a private car + /// + public enum ParkedCarApproachState { + /// + /// Citizen is not approaching their parked car + /// + None, + /// + /// Citizen is currently approaching their parked car + /// + Approaching, + /// + /// Citizen has approaching their parked car + /// + Approached, + /// + /// Citizen failed to approach their parked car + /// + Failure + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs index 2f215c1ed..8b867fa7a 100644 --- a/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs @@ -1,24 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - /// - /// Represents the reason why a parked car could not be spawned - /// - public enum ParkingUnableReason { - /// - /// Parked car could be spawned - /// - None, - /// - /// No free parking space was found - /// - NoSpaceFound, - /// - /// The maximum allowed number of parked vehicles has been reached - /// - LimitHit - } -} +namespace TrafficManager.API.Traffic.Enums { + /// + /// Represents the reason why a parked car could not be spawned + /// + public enum ParkingUnableReason { + /// + /// Parked car could be spawned + /// + None, + /// + /// No free parking space was found + /// + NoSpaceFound, + /// + /// The maximum allowed number of parked vehicles has been reached + /// + LimitHit + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/PriorityType.cs b/TLM/TMPE.API/Traffic/Enums/PriorityType.cs index fc4dc9b2f..031db619e 100644 --- a/TLM/TMPE.API/Traffic/Enums/PriorityType.cs +++ b/TLM/TMPE.API/Traffic/Enums/PriorityType.cs @@ -1,22 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum PriorityType { - None = 0, - /// - /// Priority road - /// - Main = 1, - /// - /// Stop sign - /// - Stop = 2, - /// - /// Yield sign - /// - Yield = 3 - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum PriorityType { + None = 0, + /// + /// Priority road + /// + Main = 1, + /// + /// Stop sign + /// + Stop = 2, + /// + /// Yield sign + /// + Yield = 3 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs index 865cbf603..1a09c1a66 100644 --- a/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum SetLaneArrowUnableReason { - Invalid, - HighwayArrows, - LaneConnection, - Success - } -} +namespace TrafficManager.Traffic.Enums { + public enum SetLaneArrowUnableReason { + Invalid, + HighwayArrows, + LaneConnection, + Success + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs index 09bdfa8d7..4b3e36df5 100644 --- a/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum SetPrioritySignUnableReason { - None, - NoJunction, - HasTimedLight, - InvalidSegment, - NotIncoming - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum SetPrioritySignUnableReason { + None, + NoJunction, + HasTimedLight, + InvalidSegment, + NotIncoming + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs b/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs index 1ab230dd5..9a715d100 100644 --- a/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs +++ b/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs @@ -1,29 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum StepChangeMetric { - /// - /// Step is changed based on flow/wait comparison - /// - Default, - /// - /// Step is changed on first flow detection - /// - FirstFlow, - /// - /// Step is changed on first wait detection - /// - FirstWait, - /// - /// Step is changed if no vehicle is moving - /// - NoFlow, - /// - /// Step is changed if no vehicle is waiting - /// - NoWait, - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum StepChangeMetric { + /// + /// Step is changed based on flow/wait comparison + /// + Default, + /// + /// Step is changed on first flow detection + /// + FirstFlow, + /// + /// Step is changed on first wait detection + /// + FirstWait, + /// + /// Step is changed if no vehicle is moving + /// + NoFlow, + /// + /// Step is changed if no vehicle is waiting + /// + NoWait, + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs index 8b2357024..891c68444 100644 --- a/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum ToggleTrafficLightUnableReason { - None, - NoJunction, - HasTimedLight, - IsLevelCrossing, - InsufficientSegments - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum ToggleTrafficLightUnableReason { + None, + NoJunction, + HasTimedLight, + IsLevelCrossing, + InsufficientSegments + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs b/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs index 326f9fdc0..7581705c4 100644 --- a/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs +++ b/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum TrafficLightSimulationType { - None, - Manual, - Timed - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum TrafficLightSimulationType { + None, + Manual, + Timed + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs b/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs index 4c699fb6f..f7655b5b4 100644 --- a/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs +++ b/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs @@ -1,24 +1,24 @@ -namespace TrafficManager.Traffic.Enums { - public enum VehicleJunctionTransitState { - /// - /// Represents an unknown/ignored state - /// - None, - /// - /// Vehicle is apparoaching at a junction - /// - Approach, - /// - /// Vehicle must stop at a junction - /// - Stop, - /// - /// Vehicle is leaving the junction - /// - Leave, - /// - /// Vehicle may leave but is blocked due to traffic ahead - /// - Blocked - } +namespace TrafficManager.API.Traffic.Enums { + public enum VehicleJunctionTransitState { + /// + /// Represents an unknown/ignored state + /// + None, + /// + /// Vehicle is apparoaching at a junction + /// + Approach, + /// + /// Vehicle must stop at a junction + /// + Stop, + /// + /// Vehicle is leaving the junction + /// + Leave, + /// + /// Vehicle may leave but is blocked due to traffic ahead + /// + Blocked + } } \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs index b118ea1d9..ecde32fc4 100644 --- a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs +++ b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs @@ -1,28 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using JetBrains.Annotations; -namespace TrafficManager.Traffic.Enums { - /// - /// Represents vehicle restrictions effect strength - /// - public enum VehicleRestrictionsAggression { - /// - /// Low aggression - /// - Low = 0, - /// - /// Medium aggression - /// - Medium = 1, - /// - /// High aggression - /// - High = 2, - /// - /// Strict aggression - /// - Strict = 3 - } -} + /// + /// Represents vehicle restrictions effect strength + /// + public enum VehicleRestrictionsAggression { + /// + /// Low aggression + /// + [UsedImplicitly] + Low = 0, + /// + /// Medium aggression + /// + Medium = 1, + /// + /// High aggression + /// + [UsedImplicitly] + High = 2, + /// + /// Strict aggression + /// + Strict = 3 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs index 9591ccd51..1d03d5dd9 100644 --- a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs @@ -1,21 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum VehicleRestrictionsMode { - /// - /// Interpret bus lanes as "free for all" - /// - Unrestricted, - /// - /// Interpret bus lanes according to the configuration - /// - Configured, - /// - /// Interpret bus lanes as restricted - /// - Restricted - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum VehicleRestrictionsMode { + /// + /// Interpret bus lanes as "free for all" + /// + Unrestricted, + /// + /// Interpret bus lanes according to the configuration + /// + Configured, + /// + /// Interpret bus lanes as restricted + /// + Restricted + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs b/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs index d0c046f06..56043ca09 100644 --- a/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs +++ b/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs @@ -2,6 +2,9 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.TrafficLight.Data { + using API.Traffic.Enums; + using API.TrafficLight; + public struct TrafficLightSimulation { /// /// Timed traffic light by node id diff --git a/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs b/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs index 129c38506..591457ca1 100644 --- a/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs +++ b/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs @@ -6,6 +6,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.TrafficLight { + using API.Traffic.Enums; + public interface ICustomSegmentLight : ICloneable { // TODO documentation ushort NodeId { get; } diff --git a/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs b/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs index c776f5865..2635b086f 100644 --- a/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs +++ b/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs @@ -1,43 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Manager; -using TrafficManager.Traffic; +namespace TrafficManager.API.TrafficLight { + using System; + using System.Collections.Generic; + using Traffic.Enums; + using TrafficManager.Manager; + using TrafficManager.Traffic; + using TrafficManager.TrafficLight; -namespace TrafficManager.TrafficLight { - public interface ICustomSegmentLights : ICloneable, ISegmentEndId { - // TODO documentation - ushort NodeId { get; } - IDictionary CustomLights { get; } - RoadBaseAI.TrafficLightState AutoPedestrianLightState { get; set; } // TODO should not be writable - bool InvalidPedestrianLight { get; set; } // TODO improve & remove - RoadBaseAI.TrafficLightState? PedestrianLightState { get; set; } - RoadBaseAI.TrafficLightState? InternalPedestrianLightState { get; } - bool ManualPedestrianMode { get; set; } - LinkedList VehicleTypes { get; } // TODO improve & remove - ExtVehicleType?[] VehicleTypeByLaneIndex { get; } + public interface ICustomSegmentLights : ICloneable, ISegmentEndId { + // TODO documentation + ushort NodeId { get; } + IDictionary CustomLights { get; } + RoadBaseAI.TrafficLightState AutoPedestrianLightState { get; set; } // TODO should not be writable + bool InvalidPedestrianLight { get; set; } // TODO improve & remove + RoadBaseAI.TrafficLightState? PedestrianLightState { get; set; } + RoadBaseAI.TrafficLightState? InternalPedestrianLightState { get; } + bool ManualPedestrianMode { get; set; } + LinkedList VehicleTypes { get; } // TODO improve & remove + ExtVehicleType?[] VehicleTypeByLaneIndex { get; } - void CalculateAutoPedestrianLightState(ref NetNode node, bool propagate = true); - bool IsAnyGreen(); - bool IsAnyInTransition(); - bool IsAnyLeftGreen(); - bool IsAnyMainGreen(); - bool IsAnyRightGreen(); - bool IsAllLeftRed(); - bool IsAllMainRed(); - bool IsAllRightRed(); - void UpdateVisuals(); - uint LastChange(); - void MakeRed(); - void MakeRedOrGreen(); - void ChangeLightPedestrian(); - void SetLights(RoadBaseAI.TrafficLightState lightState); - void SetLights(ICustomSegmentLights otherLights); - ICustomSegmentLight GetCustomLight(byte laneIndex); - ICustomSegmentLight GetCustomLight(ExtVehicleType vehicleType); - bool Relocate(ushort segmentId, bool startNode, ICustomSegmentLightsManager lightsManager); - ICustomSegmentLights Clone(ICustomSegmentLightsManager newLightsManager, bool performHousekeeping = true); - void Housekeeping(bool mayDelete, bool calculateAutoPedLight); - } -} + void CalculateAutoPedestrianLightState(ref NetNode node, bool propagate = true); + bool IsAnyGreen(); + bool IsAnyInTransition(); + bool IsAnyLeftGreen(); + bool IsAnyMainGreen(); + bool IsAnyRightGreen(); + bool IsAllLeftRed(); + bool IsAllMainRed(); + bool IsAllRightRed(); + void UpdateVisuals(); + uint LastChange(); + void MakeRed(); + void MakeRedOrGreen(); + void ChangeLightPedestrian(); + void SetLights(RoadBaseAI.TrafficLightState lightState); + void SetLights(ICustomSegmentLights otherLights); + ICustomSegmentLight GetCustomLight(byte laneIndex); + ICustomSegmentLight GetCustomLight(ExtVehicleType vehicleType); + bool Relocate(ushort segmentId, bool startNode, ICustomSegmentLightsManager lightsManager); + ICustomSegmentLights Clone(ICustomSegmentLightsManager newLightsManager, bool performHousekeeping = true); + void Housekeeping(bool mayDelete, bool calculateAutoPedLight); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs b/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs index 78e73bc7c..94f860ace 100644 --- a/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs +++ b/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs @@ -1,45 +1,45 @@ -using System.Collections.Generic; -using CSUtil.Commons; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using TrafficManager.Util; +namespace TrafficManager.API.TrafficLight { + using System.Collections.Generic; + using CSUtil.Commons; + using Traffic.Enums; + using TrafficManager.Traffic.Enums; + using TrafficManager.TrafficLight; -namespace TrafficManager.TrafficLight { - public interface ITimedTrafficLights { - IDictionary> Directions { get; } - ushort NodeId { get; } - ushort MasterNodeId { get; set; } // TODO private set - short RotationOffset { get; } - int CurrentStep { get; set; } - bool TestMode { get; set; } // TODO private set - IList NodeGroup { get; set; } // TODO private set + public interface ITimedTrafficLights { + IDictionary> Directions { get; } + ushort NodeId { get; } + ushort MasterNodeId { get; set; } // TODO private set + short RotationOffset { get; } + int CurrentStep { get; set; } + bool TestMode { get; set; } // TODO private set + IList NodeGroup { get; set; } // TODO private set - ITimedTrafficLightsStep AddStep(int minTime, int maxTime, StepChangeMetric changeMetric, float waitFlowBalance, bool makeRed = false); - long CheckNextChange(ushort segmentId, bool startNode, ExtVehicleType vehicleType, int lightType); - ITimedTrafficLightsStep GetStep(int stepId); - bool Housekeeping(); // TODO improve & remove - bool IsMasterNode(); - bool IsStarted(); - bool IsInTestMode(); - void SetTestMode(bool testMode); - void Destroy(); - ITimedTrafficLights MasterLights(); - void MoveStep(int oldPos, int newPos); - int NumSteps(); - void RemoveStep(int id); - void ResetSteps(); - void RotateLeft(); - void RotateRight(); - void Join(ITimedTrafficLights otherTimedLight); - void PasteSteps(ITimedTrafficLights sourceTimedLight); - void ChangeLightMode(ushort segmentId, ExtVehicleType vehicleType, LightMode mode); - void SetLights(bool noTransition = false); - void SimulationStep(); - void SkipStep(bool setLights = true, int prevStepRefIndex = -1); - void Start(); - void Start(int step); - void Stop(); - void OnGeometryUpdate(); - void RemoveNodeFromGroup(ushort otherNodeId); - } + ITimedTrafficLightsStep AddStep(int minTime, int maxTime, StepChangeMetric changeMetric, float waitFlowBalance, bool makeRed = false); + long CheckNextChange(ushort segmentId, bool startNode, ExtVehicleType vehicleType, int lightType); + ITimedTrafficLightsStep GetStep(int stepId); + bool Housekeeping(); // TODO improve & remove + bool IsMasterNode(); + bool IsStarted(); + bool IsInTestMode(); + void SetTestMode(bool testMode); + void Destroy(); + ITimedTrafficLights MasterLights(); + void MoveStep(int oldPos, int newPos); + int NumSteps(); + void RemoveStep(int id); + void ResetSteps(); + void RotateLeft(); + void RotateRight(); + void Join(ITimedTrafficLights otherTimedLight); + void PasteSteps(ITimedTrafficLights sourceTimedLight); + void ChangeLightMode(ushort segmentId, ExtVehicleType vehicleType, LightMode mode); + void SetLights(bool noTransition = false); + void SimulationStep(); + void SkipStep(bool setLights = true, int prevStepRefIndex = -1); + void Start(); + void Start(int step); + void Stop(); + void OnGeometryUpdate(); + void RemoveNodeFromGroup(ushort otherNodeId); + } } \ No newline at end of file diff --git a/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs b/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs index 0957c83f0..f27e767ec 100644 --- a/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs +++ b/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs @@ -1,41 +1,41 @@ -using System.Collections.Generic; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +namespace TrafficManager.API.TrafficLight { + using System.Collections.Generic; + using Traffic.Enums; + using TrafficManager.Manager; + using TrafficManager.Traffic.Enums; -namespace TrafficManager.TrafficLight { - public interface ITimedTrafficLightsStep : ICustomSegmentLightsManager { - // TODO documentation - IDictionary CustomSegmentLights { get; } - LinkedList InvalidSegmentLights { get; } - int PreviousStepRefIndex { get; set; } - int NextStepRefIndex { get; set; } - int MinTime { get; set; } - int MaxTime { get; set; } - StepChangeMetric ChangeMetric { get; set; } - float WaitFlowBalance { get; set; } - float CurrentWait { get; } - float CurrentFlow { get; } + public interface ITimedTrafficLightsStep : ICustomSegmentLightsManager { + // TODO documentation + IDictionary CustomSegmentLights { get; } + LinkedList InvalidSegmentLights { get; } + int PreviousStepRefIndex { get; set; } + int NextStepRefIndex { get; set; } + int MinTime { get; set; } + int MaxTime { get; set; } + StepChangeMetric ChangeMetric { get; set; } + float WaitFlowBalance { get; set; } + float CurrentWait { get; } + float CurrentFlow { get; } - void CalcWaitFlow(bool countOnlyMovingIfGreen, int stepRefIndex, out float wait, out float flow); - RoadBaseAI.TrafficLightState GetLightState(ushort segmentId, ExtVehicleType vehicleType, int lightType); - float GetMetric(float flow, float wait); - ICustomSegmentLights GetSegmentLights(ushort segmentId); - long MaxTimeRemaining(); - long MinTimeRemaining(); - bool IsInStartTransition(); - bool IsInEndTransition(); - bool IsEndTransitionDone(); - bool RelocateSegmentLights(ushort sourceSegmentId, ushort targetSegmentId); - new ICustomSegmentLights RemoveSegmentLights(ushort segmentId); - bool SetSegmentLights(ushort segmentId, ICustomSegmentLights lights); - void SetStepDone(); - bool ShouldGoToNextStep(float flow, float wait, out float metric); - void Start(int previousStepRefIndex = -1); - bool StepDone(bool updateValues); - string ToString(); - void UpdateLights(); - void UpdateLiveLights(); - void UpdateLiveLights(bool noTransition); - } + void CalcWaitFlow(bool countOnlyMovingIfGreen, int stepRefIndex, out float wait, out float flow); + RoadBaseAI.TrafficLightState GetLightState(ushort segmentId, ExtVehicleType vehicleType, int lightType); + float GetMetric(float flow, float wait); + ICustomSegmentLights GetSegmentLights(ushort segmentId); + long MaxTimeRemaining(); + long MinTimeRemaining(); + bool IsInStartTransition(); + bool IsInEndTransition(); + bool IsEndTransitionDone(); + bool RelocateSegmentLights(ushort sourceSegmentId, ushort targetSegmentId); + new ICustomSegmentLights RemoveSegmentLights(ushort segmentId); + bool SetSegmentLights(ushort segmentId, ICustomSegmentLights lights); + void SetStepDone(); + bool ShouldGoToNextStep(float flow, float wait, out float metric); + void Start(int previousStepRefIndex = -1); + bool StepDone(bool updateValues); + string ToString(); + void UpdateLights(); + void UpdateLiveLights(); + void UpdateLiveLights(bool noTransition); + } } \ No newline at end of file From 6ec4c8c122344f1a7f0ce4b26deccd3527c13911 Mon Sep 17 00:00:00 2001 From: dmytro lytovchenko Date: Mon, 15 Jul 2019 02:16:06 +0200 Subject: [PATCH 141/142] ConfigData members and Config untabified --- TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs | 111 +++-- .../State/ConfigData/DynamicLaneSelection.cs | 307 +++++++------ TLM/TLM/State/ConfigData/Gameplay.cs | 21 +- TLM/TLM/State/ConfigData/Main.cs | 136 +++--- TLM/TLM/State/ConfigData/ParkingAI.cs | 201 +++++---- TLM/TLM/State/ConfigData/PathFinding.cs | 101 +++-- TLM/TLM/State/ConfigData/PriorityRules.cs | 53 ++- .../State/ConfigData/TimedTrafficLights.cs | 40 +- TLM/TLM/State/GlobalConfig.cs | 408 +++++++++--------- 9 files changed, 665 insertions(+), 713 deletions(-) diff --git a/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs b/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs index cb7f7bdfc..6e961ec6a 100644 --- a/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs +++ b/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs @@ -1,58 +1,53 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State.ConfigData { - public class AdvancedVehicleAI { - /// - /// Junction randomization for randomized lane selection - /// - public uint LaneRandomizationJunctionSel = 3; - - /// - /// Cost factor for lane randomization - /// - public float LaneRandomizationCostFactor = 1f; - - /// - /// minimum base lane changing cost - /// - public float LaneChangingBaseMinCost = 1.1f; - - /// - /// maximum base lane changing cost - /// - public float LaneChangingBaseMaxCost = 1.5f; - - /// - /// base cost for changing lanes in front of junctions - /// - public float LaneChangingJunctionBaseCost = 2f; - - /// - /// base cost for traversing junctions - /// - public float JunctionBaseCost = 0.1f; - - /// - /// > 1 lane changing cost factor - /// - public float MoreThanOneLaneChangingCostFactor = 2f; - - /// - /// Relative factor for lane traffic cost calculation - /// - public float TrafficCostFactor = 4f; - - /// - /// lane density random interval - /// - public float LaneDensityRandInterval = 20f; - - /// - /// Threshold for resetting traffic buffer - /// - public uint MaxTrafficBuffer = 10; - } -} +namespace TrafficManager.State.ConfigData { + public class AdvancedVehicleAI { + /// + /// Junction randomization for randomized lane selection + /// + public uint LaneRandomizationJunctionSel = 3; + + /// + /// Cost factor for lane randomization + /// + public float LaneRandomizationCostFactor = 1f; + + /// + /// minimum base lane changing cost + /// + public float LaneChangingBaseMinCost = 1.1f; + + /// + /// maximum base lane changing cost + /// + public float LaneChangingBaseMaxCost = 1.5f; + + /// + /// base cost for changing lanes in front of junctions + /// + public float LaneChangingJunctionBaseCost = 2f; + + /// + /// base cost for traversing junctions + /// + public float JunctionBaseCost = 0.1f; + + /// + /// > 1 lane changing cost factor + /// + public float MoreThanOneLaneChangingCostFactor = 2f; + + /// + /// Relative factor for lane traffic cost calculation + /// + public float TrafficCostFactor = 4f; + + /// + /// lane density random interval + /// + public float LaneDensityRandInterval = 20f; + + /// + /// Threshold for resetting traffic buffer + /// + public uint MaxTrafficBuffer = 10; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs b/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs index 8c19815d1..282000192 100644 --- a/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs +++ b/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs @@ -1,156 +1,151 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State.ConfigData { - public class DynamicLaneSelection { - /// - /// Maximum allowed reserved space on previous vehicle lane - /// - public float MaxReservedSpace = 0.5f; - - /// - /// Maximum allowed reserved space on previous vehicle lane (for reckless drivers) - /// - public float MaxRecklessReservedSpace = 10f; - - /// - /// Lane speed randomization interval - /// - public float LaneSpeedRandInterval = 5f; - - /// - /// Maximum number of considered lane changes - /// - public int MaxOptLaneChanges = 2; - - /// - /// Maximum allowed speed difference for safe lane changes - /// - public float MaxUnsafeSpeedDiff = 0.4f; - - /// - /// Minimum required speed improvement for safe lane changes - /// - public float MinSafeSpeedImprovement = 25f; - - /// - /// Minimum required traffic flow improvement for safe lane changes - /// - public float MinSafeTrafficImprovement = 20f; - - /// - /// Minimum relative speed (in %) where volume measurement starts - /// - public ushort VolumeMeasurementRelSpeedThreshold = 50; - - // --- - - /* - * max. reserved space: - * low = egoistic - * high = altruistic - */ - - /// - /// Minimum maximum allowed reserved space on previous vehicle lane (for regular drivers) - /// - public float MinMaxReservedSpace = 0f; - - /// - /// Maximum value for Maximum allowed reserved space on previous vehicle lane (for regular drivers) - /// - public float MaxMaxReservedSpace = 5f; - - /// - /// Minimum maximum allowed reserved space on previous vehicle lane (for reckless drivers) - /// - public float MinMaxRecklessReservedSpace = 10f; - - /// - /// Maximum maximum allowed reserved space on previous vehicle lane (for reckless drivers) - /// - public float MaxMaxRecklessReservedSpace = 50f; - - /* - * lane speed randomization interval: - * low = altruistic (driver sees the true lane speed) - * high = egoistic (driver imagines to be in the slowest queue, http://www.bbc.com/future/story/20130827-why-other-queues-move-faster) - */ - - /// - /// Minimum lane speed randomization interval - /// - public float MinLaneSpeedRandInterval = 0f; - - /// - /// Maximum lane speed randomization interval - /// - public float MaxLaneSpeedRandInterval = 25f; - - /* - * max. considered lane changes: - * low = altruistic - * high = egoistic - */ - - /// - /// Maximum number of considered lane changes - /// - public int MinMaxOptLaneChanges = 1; - - /// - /// Maximum number of considered lane changes - /// - public int MaxMaxOptLaneChanges = 3; - - /* - * max. allowed speed difference for unsafe lane changes - * low = altruistic - * high = egoistic - */ - - /// - /// Minimum maximum allowed speed difference for unsafe lane changes (in game units) - /// - public float MinMaxUnsafeSpeedDiff = 0.1f; - - /// - /// Maximum maximum allowed speed difference for unsafe lane changes (in game units) - /// - public float MaxMaxUnsafeSpeedDiff = 1f; - - /* - * min. required speed improvement for safe lane changes - * low = egoistic - * high = altruistic - */ - - /// - /// Minimum minimum required speed improvement for safe lane changes (in km/h) - /// - public float MinMinSafeSpeedImprovement = 5f; - - /// - /// Maximum minimum required speed improvement for safe lane changes (in km/h) - /// - public float MaxMinSafeSpeedImprovement = 30f; - - /* - * min. required traffic flow improvement for safe lane changes - * low = egoistic - * high = altruistic - */ - - /// - /// Minimum minimum required traffic flow improvement for safe lane changes (in %) - /// - public float MinMinSafeTrafficImprovement = 5f; - - /// - /// Maximum minimum required traffic flow improvement for safe lane changes (in %) - /// - public float MaxMinSafeTrafficImprovement = 30f; - } -} +namespace TrafficManager.State.ConfigData { + public class DynamicLaneSelection { + /// + /// Maximum allowed reserved space on previous vehicle lane + /// + public float MaxReservedSpace = 0.5f; + + /// + /// Maximum allowed reserved space on previous vehicle lane (for reckless drivers) + /// + public float MaxRecklessReservedSpace = 10f; + + /// + /// Lane speed randomization interval + /// + public float LaneSpeedRandInterval = 5f; + + /// + /// Maximum number of considered lane changes + /// + public int MaxOptLaneChanges = 2; + + /// + /// Maximum allowed speed difference for safe lane changes + /// + public float MaxUnsafeSpeedDiff = 0.4f; + + /// + /// Minimum required speed improvement for safe lane changes + /// + public float MinSafeSpeedImprovement = 25f; + + /// + /// Minimum required traffic flow improvement for safe lane changes + /// + public float MinSafeTrafficImprovement = 20f; + + /// + /// Minimum relative speed (in %) where volume measurement starts + /// + public ushort VolumeMeasurementRelSpeedThreshold = 50; + + // --- + + /* + * max. reserved space: + * low = egoistic + * high = altruistic + */ + + /// + /// Minimum maximum allowed reserved space on previous vehicle lane (for regular drivers) + /// + public float MinMaxReservedSpace = 0f; + + /// + /// Maximum value for Maximum allowed reserved space on previous vehicle lane (for regular drivers) + /// + public float MaxMaxReservedSpace = 5f; + + /// + /// Minimum maximum allowed reserved space on previous vehicle lane (for reckless drivers) + /// + public float MinMaxRecklessReservedSpace = 10f; + + /// + /// Maximum maximum allowed reserved space on previous vehicle lane (for reckless drivers) + /// + public float MaxMaxRecklessReservedSpace = 50f; + + /* + * lane speed randomization interval: + * low = altruistic (driver sees the true lane speed) + * high = egoistic (driver imagines to be in the slowest queue, http://www.bbc.com/future/story/20130827-why-other-queues-move-faster) + */ + + /// + /// Minimum lane speed randomization interval + /// + public float MinLaneSpeedRandInterval = 0f; + + /// + /// Maximum lane speed randomization interval + /// + public float MaxLaneSpeedRandInterval = 25f; + + /* + * max. considered lane changes: + * low = altruistic + * high = egoistic + */ + + /// + /// Maximum number of considered lane changes + /// + public int MinMaxOptLaneChanges = 1; + + /// + /// Maximum number of considered lane changes + /// + public int MaxMaxOptLaneChanges = 3; + + /* + * max. allowed speed difference for unsafe lane changes + * low = altruistic + * high = egoistic + */ + + /// + /// Minimum maximum allowed speed difference for unsafe lane changes (in game units) + /// + public float MinMaxUnsafeSpeedDiff = 0.1f; + + /// + /// Maximum maximum allowed speed difference for unsafe lane changes (in game units) + /// + public float MaxMaxUnsafeSpeedDiff = 1f; + + /* + * min. required speed improvement for safe lane changes + * low = egoistic + * high = altruistic + */ + + /// + /// Minimum minimum required speed improvement for safe lane changes (in km/h) + /// + public float MinMinSafeSpeedImprovement = 5f; + + /// + /// Maximum minimum required speed improvement for safe lane changes (in km/h) + /// + public float MaxMinSafeSpeedImprovement = 30f; + + /* + * min. required traffic flow improvement for safe lane changes + * low = egoistic + * high = altruistic + */ + + /// + /// Minimum minimum required traffic flow improvement for safe lane changes (in %) + /// + public float MinMinSafeTrafficImprovement = 5f; + + /// + /// Maximum minimum required traffic flow improvement for safe lane changes (in %) + /// + public float MaxMinSafeTrafficImprovement = 30f; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/Gameplay.cs b/TLM/TLM/State/ConfigData/Gameplay.cs index 48e778e80..0c845c0cf 100644 --- a/TLM/TLM/State/ConfigData/Gameplay.cs +++ b/TLM/TLM/State/ConfigData/Gameplay.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State.ConfigData { - public class Gameplay { - /// - /// Modulo value for time-varying vehicle behavior randomization - /// - public uint VehicleTimedRandModulo = 10; - } -} +namespace TrafficManager.State.ConfigData { + public class Gameplay { + /// + /// Modulo value for time-varying vehicle behavior randomization + /// + public uint VehicleTimedRandModulo = 10; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/Main.cs b/TLM/TLM/State/ConfigData/Main.cs index 53180e047..de4152520 100644 --- a/TLM/TLM/State/ConfigData/Main.cs +++ b/TLM/TLM/State/ConfigData/Main.cs @@ -1,83 +1,81 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic.Data; -using TrafficManager.UI.MainMenu; +namespace TrafficManager.State.ConfigData { + using System.Collections.Generic; + using System.Linq; + using Traffic.Data; + using UI.MainMenu; -namespace TrafficManager.State.ConfigData { - public class Main { - /// - /// Main menu button position - /// - public int MainMenuButtonX = 464; - public int MainMenuButtonY = 10; - public bool MainMenuButtonPosLocked = false; + public class Main { + /// + /// Main menu button position + /// + public int MainMenuButtonX = 464; + public int MainMenuButtonY = 10; + public bool MainMenuButtonPosLocked = false; - /// - /// Main menu position - /// - public int MainMenuX = MainMenuPanel.DEFAULT_MENU_X; - public int MainMenuY = MainMenuPanel.DEFAULT_MENU_Y; - public bool MainMenuPosLocked = false; + /// + /// Main menu position + /// + public int MainMenuX = MainMenuPanel.DEFAULT_MENU_X; + public int MainMenuY = MainMenuPanel.DEFAULT_MENU_Y; + public bool MainMenuPosLocked = false; - /// - /// Already displayed tutorial messages - /// - public string[] DisplayedTutorialMessages = new string[0]; + /// + /// Already displayed tutorial messages + /// + public string[] DisplayedTutorialMessages = new string[0]; - /// - /// Determines if tutorial messages shall show up - /// - public bool EnableTutorial = true; + /// + /// Determines if tutorial messages shall show up + /// + public bool EnableTutorial = true; - /// - /// Determines if the main menu shall be displayed in a tiny format - /// - public bool TinyMainMenu = true; + /// + /// Determines if the main menu shall be displayed in a tiny format + /// + public bool TinyMainMenu = true; - /// - /// User interface transparency - /// - public byte GuiTransparency = 30; + /// + /// User interface transparency + /// + public byte GuiTransparency = 30; - /// - /// Overlay transparency - /// - public byte OverlayTransparency = 40; + /// + /// Overlay transparency + /// + public byte OverlayTransparency = 40; - /// - /// Extended mod compatibility check - /// - public bool ShowCompatibilityCheckErrorMessage = false; + /// + /// Extended mod compatibility check + /// + public bool ShowCompatibilityCheckErrorMessage = false; - /// - /// Shows warning dialog if any incompatible mods detected - /// - public bool ScanForKnownIncompatibleModsAtStartup = true; + /// + /// Shows warning dialog if any incompatible mods detected + /// + public bool ScanForKnownIncompatibleModsAtStartup = true; - /// - /// Skip disabled mods while running incompatible mod detector - /// - public bool IgnoreDisabledMods = true; + /// + /// Skip disabled mods while running incompatible mod detector + /// + public bool IgnoreDisabledMods = true; - /// - /// Prefer Miles per hour instead of Kmph (affects speed limits display - /// but internally Kmph are still used). - /// - public bool DisplaySpeedLimitsMph = false; + /// + /// Prefer Miles per hour instead of Kmph (affects speed limits display + /// but internally Kmph are still used). + /// + public bool DisplaySpeedLimitsMph = false; - /// - /// Selected theme for road signs when MPH is active. - /// - public MphSignStyle MphRoadSignStyle = MphSignStyle.SquareUS; + /// + /// Selected theme for road signs when MPH is active. + /// + public MphSignStyle MphRoadSignStyle = MphSignStyle.SquareUS; - public void AddDisplayedTutorialMessage(string messageKey) { - HashSet newMessages = DisplayedTutorialMessages != null - ? new HashSet(DisplayedTutorialMessages) - : new HashSet(); - newMessages.Add(messageKey); - DisplayedTutorialMessages = newMessages.ToArray(); - } - } + public void AddDisplayedTutorialMessage(string messageKey) { + HashSet newMessages = DisplayedTutorialMessages != null + ? new HashSet(DisplayedTutorialMessages) + : new HashSet(); + newMessages.Add(messageKey); + DisplayedTutorialMessages = newMessages.ToArray(); + } + } } \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/ParkingAI.cs b/TLM/TLM/State/ConfigData/ParkingAI.cs index 094052c87..89ffaba58 100644 --- a/TLM/TLM/State/ConfigData/ParkingAI.cs +++ b/TLM/TLM/State/ConfigData/ParkingAI.cs @@ -1,103 +1,98 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State.ConfigData { - public class ParkingAI { - /// - /// Target position randomization to allow opposite road-side parking - /// - public uint ParkingSpacePositionRand = 32; - - /// - /// parking space search in vicinity is randomized. Cims do not always select the nearest parking space possible. - /// A value of 1u always selects the nearest parking space. - /// A value of 2u selects the nearest parking space with 50% chance, the next one with 25%, then 12.5% and so on. - /// A value of 3u selects the nearest parking space with 66.67% chance, the next one with 22.22%, then 7.4% and so on. - /// A value of 4u selects the nearest parking space with 75% chance, the next one with 18.75%, then 4.6875% and so on. - /// A value of N selects the nearest parking space with (N-1)/N chance, the next one with (1-(N-1)/N)*(N-1)/N, then (1-(N-1)/N)^2*(N-1)/N and so on. - /// - public uint VicinityParkingSpaceSelectionRand = 4u; - - /// - /// maximum number of parking attempts for passenger cars - /// - public int MaxParkingAttempts = 10; - - /// - /// maximum required squared distance between citizen instance and parked vehicle before the parked car is turned into a vehicle - /// - public float MaxParkedCarInstanceSwitchSqrDistance = 6f; - - /// - /// maximum distance between building and pedestrian lane - /// - public float MaxBuildingToPedestrianLaneDistance = 96f; - - /// - /// Maximum allowed distance between home/source building and parked car when travelling home without forced to use the car - /// - public float MaxParkedCarDistanceToHome = 256f; - - /// - /// minimum required distance between target building and parked car for using a car - /// - public float MaxParkedCarDistanceToBuilding = 512f; - - /// - /// public transport demand increment on path-find failure - /// - public uint PublicTransportDemandIncrement = 10u; - - /// - /// public transport demand increment if waiting time was exceeded - /// - public uint PublicTransportDemandWaitingIncrement = 3u; - - /// - /// public transport demand decrement on simulation step - /// - public uint PublicTransportDemandDecrement = 1u; - - /// - /// public transport demand decrement on path-find success - /// - public uint PublicTransportDemandUsageDecrement = 7u; - - /// - /// parking space demand decrement on simulation step - /// - public uint ParkingSpaceDemandDecrement = 1u; - - /// - /// minimum parking space demand delta when a passenger car could be spawned - /// - public int MinSpawnedCarParkingSpaceDemandDelta = -5; - - /// - /// maximum parking space demand delta when a passenger car could be spawned - /// - public int MaxSpawnedCarParkingSpaceDemandDelta = 3; - - /// - /// minimum parking space demand delta when a parking spot could be found - /// - public int MinFoundParkPosParkingSpaceDemandDelta = -5; - - /// - /// maximum parking space demand delta when a parking spot could be found - /// - public int MaxFoundParkPosParkingSpaceDemandDelta = 3; - - /// - /// parking space demand increment when no parking spot could be found while trying to park - /// - public uint FailedParkingSpaceDemandIncrement = 5u; - - /// - /// parking space demand increment when no parking spot could be found while trying to spawn a parked vehicle - /// - public uint FailedSpawnParkingSpaceDemandIncrement = 10u; - } -} +namespace TrafficManager.State.ConfigData { + public class ParkingAI { + /// + /// Target position randomization to allow opposite road-side parking + /// + public uint ParkingSpacePositionRand = 32; + + /// + /// parking space search in vicinity is randomized. Cims do not always select the nearest parking space possible. + /// A value of 1u always selects the nearest parking space. + /// A value of 2u selects the nearest parking space with 50% chance, the next one with 25%, then 12.5% and so on. + /// A value of 3u selects the nearest parking space with 66.67% chance, the next one with 22.22%, then 7.4% and so on. + /// A value of 4u selects the nearest parking space with 75% chance, the next one with 18.75%, then 4.6875% and so on. + /// A value of N selects the nearest parking space with (N-1)/N chance, the next one with (1-(N-1)/N)*(N-1)/N, then (1-(N-1)/N)^2*(N-1)/N and so on. + /// + public uint VicinityParkingSpaceSelectionRand = 4u; + + /// + /// maximum number of parking attempts for passenger cars + /// + public int MaxParkingAttempts = 10; + + /// + /// maximum required squared distance between citizen instance and parked vehicle before the parked car is turned into a vehicle + /// + public float MaxParkedCarInstanceSwitchSqrDistance = 6f; + + /// + /// maximum distance between building and pedestrian lane + /// + public float MaxBuildingToPedestrianLaneDistance = 96f; + + /// + /// Maximum allowed distance between home/source building and parked car when travelling home without forced to use the car + /// + public float MaxParkedCarDistanceToHome = 256f; + + /// + /// minimum required distance between target building and parked car for using a car + /// + public float MaxParkedCarDistanceToBuilding = 512f; + + /// + /// public transport demand increment on path-find failure + /// + public uint PublicTransportDemandIncrement = 10u; + + /// + /// public transport demand increment if waiting time was exceeded + /// + public uint PublicTransportDemandWaitingIncrement = 3u; + + /// + /// public transport demand decrement on simulation step + /// + public uint PublicTransportDemandDecrement = 1u; + + /// + /// public transport demand decrement on path-find success + /// + public uint PublicTransportDemandUsageDecrement = 7u; + + /// + /// parking space demand decrement on simulation step + /// + public uint ParkingSpaceDemandDecrement = 1u; + + /// + /// minimum parking space demand delta when a passenger car could be spawned + /// + public int MinSpawnedCarParkingSpaceDemandDelta = -5; + + /// + /// maximum parking space demand delta when a passenger car could be spawned + /// + public int MaxSpawnedCarParkingSpaceDemandDelta = 3; + + /// + /// minimum parking space demand delta when a parking spot could be found + /// + public int MinFoundParkPosParkingSpaceDemandDelta = -5; + + /// + /// maximum parking space demand delta when a parking spot could be found + /// + public int MaxFoundParkPosParkingSpaceDemandDelta = 3; + + /// + /// parking space demand increment when no parking spot could be found while trying to park + /// + public uint FailedParkingSpaceDemandIncrement = 5u; + + /// + /// parking space demand increment when no parking spot could be found while trying to spawn a parked vehicle + /// + public uint FailedSpawnParkingSpaceDemandIncrement = 10u; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/PathFinding.cs b/TLM/TLM/State/ConfigData/PathFinding.cs index 181786797..2dd176dfb 100644 --- a/TLM/TLM/State/ConfigData/PathFinding.cs +++ b/TLM/TLM/State/ConfigData/PathFinding.cs @@ -1,53 +1,48 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State.ConfigData { - public class PathFinding { - /// - /// penalty for busses not driving on bus lanes - /// - public float PublicTransportLanePenalty = 10f; - - /// - /// reward for public transport staying on transport lane - /// - public float PublicTransportLaneReward = 0.1f; - - /// - /// maximum penalty for heavy vehicles driving on an inner lane - /// - public float HeavyVehicleMaxInnerLanePenalty = 0.5f; - - /// - /// Junction randomization for randomized lane selection - /// - public uint HeavyVehicleInnerLanePenaltySegmentSel = 3; - - /// - /// artifical lane distance for vehicles that change to lanes which have an incompatible lane arrow configuration - /// - public byte IncompatibleLaneDistance = 2; - - /// - /// artifical lane distance for u-turns - /// - public int UturnLaneDistance = 2; - - /// - /// Maximum walking distance - /// - public float MaxWalkingDistance = 2500f; - - /// - /// Minimum penalty for entering public transport vehicles - /// - public float PublicTransportTransitionMinPenalty = 0f; - - /// - /// Maximum penalty for entering public transport vehicles - /// - public float PublicTransportTransitionMaxPenalty = 100f; - } -} +namespace TrafficManager.State.ConfigData { + public class PathFinding { + /// + /// penalty for busses not driving on bus lanes + /// + public float PublicTransportLanePenalty = 10f; + + /// + /// reward for public transport staying on transport lane + /// + public float PublicTransportLaneReward = 0.1f; + + /// + /// maximum penalty for heavy vehicles driving on an inner lane + /// + public float HeavyVehicleMaxInnerLanePenalty = 0.5f; + + /// + /// Junction randomization for randomized lane selection + /// + public uint HeavyVehicleInnerLanePenaltySegmentSel = 3; + + /// + /// artifical lane distance for vehicles that change to lanes which have an incompatible lane arrow configuration + /// + public byte IncompatibleLaneDistance = 2; + + /// + /// artifical lane distance for u-turns + /// + public int UturnLaneDistance = 2; + + /// + /// Maximum walking distance + /// + public float MaxWalkingDistance = 2500f; + + /// + /// Minimum penalty for entering public transport vehicles + /// + public float PublicTransportTransitionMinPenalty = 0f; + + /// + /// Maximum penalty for entering public transport vehicles + /// + public float PublicTransportTransitionMaxPenalty = 100f; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/PriorityRules.cs b/TLM/TLM/State/ConfigData/PriorityRules.cs index 3a478c91f..fca762803 100644 --- a/TLM/TLM/State/ConfigData/PriorityRules.cs +++ b/TLM/TLM/State/ConfigData/PriorityRules.cs @@ -1,33 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.State.ConfigData { + public class PriorityRules { + /// + /// maximum incoming vehicle square distance to junction for priority signs + /// + public float MaxPriorityCheckSqrDist = 225f; -namespace TrafficManager.State.ConfigData { - public class PriorityRules { - /// - /// maximum incoming vehicle square distance to junction for priority signs - /// - public float MaxPriorityCheckSqrDist = 225f; + /// + /// maximum junction approach time for priority signs + /// + public float MaxPriorityApproachTime = 15f; - /// - /// maximum junction approach time for priority signs - /// - public float MaxPriorityApproachTime = 15f; + /// + /// maximum waiting time at priority signs + /// + public uint MaxPriorityWaitTime = 100; - /// - /// maximum waiting time at priority signs - /// - public uint MaxPriorityWaitTime = 100; + /// + /// Maximum yield velocity + /// + public float MaxYieldVelocity = 2.5f; - /// - /// Maximum yield velocity - /// - public float MaxYieldVelocity = 2.5f; - - /// - /// Maximum stop velocity - /// - public float MaxStopVelocity = 0.1f; - } -} + /// + /// Maximum stop velocity + /// + public float MaxStopVelocity = 0.1f; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/TimedTrafficLights.cs b/TLM/TLM/State/ConfigData/TimedTrafficLights.cs index 4a9b2c25b..2d5a8bdf3 100644 --- a/TLM/TLM/State/ConfigData/TimedTrafficLights.cs +++ b/TLM/TLM/State/ConfigData/TimedTrafficLights.cs @@ -1,26 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.TrafficLight; +namespace TrafficManager.State.ConfigData { + using API.Traffic.Enums; -namespace TrafficManager.State.ConfigData { - using API.Traffic.Enums; + public class TimedTrafficLights { + /// + /// TTL wait/flow calculation mode + /// + public FlowWaitCalcMode FlowWaitCalcMode = FlowWaitCalcMode.Mean; - public class TimedTrafficLights { - /// - /// TTL wait/flow calculation mode - /// - public FlowWaitCalcMode FlowWaitCalcMode = FlowWaitCalcMode.Mean; + /// + /// Default TTL flow-to-wait ratio + /// + public float FlowToWaitRatio = 0.8f; - /// - /// Default TTL flow-to-wait ratio - /// - public float FlowToWaitRatio = 0.8f; - - /// - /// TTL smoothing factor for flowing/waiting vehicles - /// - public float SmoothingFactor = 0.1f; - } -} + /// + /// TTL smoothing factor for flowing/waiting vehicles + /// + public float SmoothingFactor = 0.1f; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/GlobalConfig.cs b/TLM/TLM/State/GlobalConfig.cs index 10e85dc87..1efa3150c 100644 --- a/TLM/TLM/State/GlobalConfig.cs +++ b/TLM/TLM/State/GlobalConfig.cs @@ -1,237 +1,227 @@ -using ColossalFramework; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Xml; -using System.Xml.Serialization; -using TrafficManager.Manager; -using TrafficManager.State.ConfigData; -using TrafficManager.Traffic; -using TrafficManager.TrafficLight; -using TrafficManager.Util; - -namespace TrafficManager.State { - [XmlRootAttribute("GlobalConfig", Namespace = "http://www.viathinksoft.de/tmpe", IsNullable = false)] - public class GlobalConfig : GenericObservable { - public const string FILENAME = "TMPE_GlobalConfig.xml"; - public const string BACKUP_FILENAME = FILENAME + ".bak"; - private static int LATEST_VERSION = 17; - - public static GlobalConfig Instance { - get { - return instance; - } - private set { - if (value != null && instance != null) { - value.Observers = instance.Observers; - value.ObserverLock = instance.ObserverLock; - } - instance = value; - if (instance != null) { - instance.NotifyObservers(instance); - } - } - } - - private static GlobalConfig instance = null; - - //private object ObserverLock = new object(); - - /// - /// Holds a list of observers which are being notified as soon as the configuration is updated - /// - //private List> observers = new List>(); - - static GlobalConfig() { - Reload(); - } - - internal static void OnLevelUnloading() { - } - - private static DateTime ModifiedTime = DateTime.MinValue; - - /// - /// Configuration version - /// - public int Version = LATEST_VERSION; - - /// - /// Language to use (if null then the game's language is being used) - /// - public string LanguageCode = null; +namespace TrafficManager.State { + using System; + using System.IO; + using System.Xml.Serialization; + using CSUtil.Commons; + using State.ConfigData; + using Util; + + [XmlRootAttribute("GlobalConfig", Namespace = "http://www.viathinksoft.de/tmpe", IsNullable = false)] + public class GlobalConfig : GenericObservable { + public const string FILENAME = "TMPE_GlobalConfig.xml"; + public const string BACKUP_FILENAME = FILENAME + ".bak"; + private static int LATEST_VERSION = 17; + + public static GlobalConfig Instance { + get => instance; + private set { + if (value != null && instance != null) { + value.Observers = instance.Observers; + value.ObserverLock = instance.ObserverLock; + } + + instance = value; + if (instance != null) { + instance.NotifyObservers(instance); + } + } + } + + private static GlobalConfig instance = null; + + //private object ObserverLock = new object(); + + /// + /// Holds a list of observers which are being notified as soon as the configuration is updated + /// + //private List> observers = new List>(); + + static GlobalConfig() { + Reload(); + } + + internal static void OnLevelUnloading() { + } + + private static DateTime ModifiedTime = DateTime.MinValue; + + /// + /// Configuration version + /// + public int Version = LATEST_VERSION; + + /// + /// Language to use (if null then the game's language is being used) + /// + public string LanguageCode = null; #if DEBUG - public Debug Debug = new Debug(); + public Debug Debug = new Debug(); #endif - public AdvancedVehicleAI AdvancedVehicleAI = new AdvancedVehicleAI(); + public AdvancedVehicleAI AdvancedVehicleAI = new AdvancedVehicleAI(); - public DynamicLaneSelection DynamicLaneSelection = new DynamicLaneSelection(); + public DynamicLaneSelection DynamicLaneSelection = new DynamicLaneSelection(); - public Gameplay Gameplay = new Gameplay(); + public Gameplay Gameplay = new Gameplay(); - public Main Main = new Main(); + public Main Main = new Main(); - public ParkingAI ParkingAI = new ParkingAI(); + public ParkingAI ParkingAI = new ParkingAI(); - public PathFinding PathFinding = new PathFinding(); + public PathFinding PathFinding = new PathFinding(); - public PriorityRules PriorityRules = new PriorityRules(); + public PriorityRules PriorityRules = new PriorityRules(); - public TimedTrafficLights TimedTrafficLights = new TimedTrafficLights(); + public TimedTrafficLights TimedTrafficLights = new TimedTrafficLights(); - internal static void WriteConfig() { - ModifiedTime = WriteConfig(Instance); - } + internal static void WriteConfig() { + ModifiedTime = WriteConfig(Instance); + } - private static GlobalConfig WriteDefaultConfig(GlobalConfig oldConfig, bool resetAll, out DateTime modifiedTime) { - Log._Debug($"Writing default config..."); - GlobalConfig conf = new GlobalConfig(); - if (!resetAll && oldConfig != null) { - conf.Main.MainMenuButtonX = oldConfig.Main.MainMenuButtonX; - conf.Main.MainMenuButtonY = oldConfig.Main.MainMenuButtonY; + private static GlobalConfig WriteDefaultConfig(GlobalConfig oldConfig, bool resetAll, out DateTime modifiedTime) { + Log._Debug($"Writing default config..."); + GlobalConfig conf = new GlobalConfig(); + if (!resetAll && oldConfig != null) { + conf.Main.MainMenuButtonX = oldConfig.Main.MainMenuButtonX; + conf.Main.MainMenuButtonY = oldConfig.Main.MainMenuButtonY; - conf.Main.MainMenuX = oldConfig.Main.MainMenuX; - conf.Main.MainMenuY = oldConfig.Main.MainMenuY; + conf.Main.MainMenuX = oldConfig.Main.MainMenuX; + conf.Main.MainMenuY = oldConfig.Main.MainMenuY; - conf.Main.MainMenuButtonPosLocked = oldConfig.Main.MainMenuButtonPosLocked; - conf.Main.MainMenuPosLocked = oldConfig.Main.MainMenuPosLocked; + conf.Main.MainMenuButtonPosLocked = oldConfig.Main.MainMenuButtonPosLocked; + conf.Main.MainMenuPosLocked = oldConfig.Main.MainMenuPosLocked; - conf.Main.GuiTransparency = oldConfig.Main.GuiTransparency; - conf.Main.OverlayTransparency = oldConfig.Main.OverlayTransparency; + conf.Main.GuiTransparency = oldConfig.Main.GuiTransparency; + conf.Main.OverlayTransparency = oldConfig.Main.OverlayTransparency; - conf.Main.TinyMainMenu = oldConfig.Main.TinyMainMenu; + conf.Main.TinyMainMenu = oldConfig.Main.TinyMainMenu; - conf.Main.EnableTutorial = oldConfig.Main.EnableTutorial; - conf.Main.DisplayedTutorialMessages = oldConfig.Main.DisplayedTutorialMessages; - } - modifiedTime = WriteConfig(conf); - return conf; - } + conf.Main.EnableTutorial = oldConfig.Main.EnableTutorial; + conf.Main.DisplayedTutorialMessages = oldConfig.Main.DisplayedTutorialMessages; + } + modifiedTime = WriteConfig(conf); + return conf; + } - private static DateTime WriteConfig(GlobalConfig config, string filename=FILENAME) { - try { - Log.Info($"Writing global config to file '{filename}'..."); - XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); - using (TextWriter writer = new StreamWriter(filename)) { - serializer.Serialize(writer, config); - } - } catch (Exception e) { - Log.Error($"Could not write global config: {e.ToString()}"); - } + private static DateTime WriteConfig(GlobalConfig config, string filename=FILENAME) { + try { + Log.Info($"Writing global config to file '{filename}'..."); + XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); + using (TextWriter writer = new StreamWriter(filename)) { + serializer.Serialize(writer, config); + } + } catch (Exception e) { + Log.Error($"Could not write global config: {e.ToString()}"); + } - try { - return File.GetLastWriteTime(FILENAME); - } catch (Exception e) { - Log.Warning($"Could not determine modification date of global config: {e.ToString()}"); - return DateTime.Now; - } - } + try { + return File.GetLastWriteTime(FILENAME); + } catch (Exception e) { + Log.Warning($"Could not determine modification date of global config: {e.ToString()}"); + return DateTime.Now; + } + } - public static GlobalConfig Load(out DateTime modifiedTime) { - try { - modifiedTime = File.GetLastWriteTime(FILENAME); + public static GlobalConfig Load(out DateTime modifiedTime) { + try { + modifiedTime = File.GetLastWriteTime(FILENAME); - Log.Info($"Loading global config from file '{FILENAME}'..."); - using (FileStream fs = new FileStream(FILENAME, FileMode.Open)) { - XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); - Log.Info($"Global config loaded."); - GlobalConfig conf = (GlobalConfig)serializer.Deserialize(fs); - if (LoadingExtension.IsGameLoaded + Log.Info($"Loading global config from file '{FILENAME}'..."); + using (FileStream fs = new FileStream(FILENAME, FileMode.Open)) { + XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); + Log.Info($"Global config loaded."); + GlobalConfig conf = (GlobalConfig)serializer.Deserialize(fs); + if (LoadingExtension.IsGameLoaded #if DEBUG - && !conf.Debug.Switches[10] + && !conf.Debug.Switches[10] #endif - ) { - Constants.ManagerFactory.RoutingManager.RequestFullRecalculation(); - } + ) { + Constants.ManagerFactory.RoutingManager.RequestFullRecalculation(); + } #if DEBUG - if (conf.Debug == null) { - conf.Debug = new Debug(); - } + if (conf.Debug == null) { + conf.Debug = new Debug(); + } #endif - if (conf.AdvancedVehicleAI == null) { - conf.AdvancedVehicleAI = new AdvancedVehicleAI(); - } - - if (conf.DynamicLaneSelection == null) { - conf.DynamicLaneSelection = new DynamicLaneSelection(); - } - - if (conf.Gameplay == null) { - conf.Gameplay = new Gameplay(); - } - - if (conf.ParkingAI == null) { - conf.ParkingAI = new ParkingAI(); - } - - if (conf.PathFinding == null) { - conf.PathFinding = new PathFinding(); - } - - if (conf.PriorityRules == null) { - conf.PriorityRules = new PriorityRules(); - } - - if (conf.TimedTrafficLights == null) { - conf.TimedTrafficLights = new TimedTrafficLights(); - } - - return conf; - } - } catch (Exception e) { - Log.Warning($"Could not load global config: {e} Generating default config."); - return WriteDefaultConfig(null, false, out modifiedTime); - } - } - - public static void Reload(bool checkVersion=true) { - DateTime modifiedTime; - GlobalConfig conf = Load(out modifiedTime); - if (checkVersion && conf.Version != -1 && conf.Version < LATEST_VERSION) { - // backup old config and reset - string filename = BACKUP_FILENAME; - try { - int backupIndex = 0; - while (File.Exists(filename)) { - filename = BACKUP_FILENAME + "." + backupIndex; - ++backupIndex; - } - WriteConfig(conf, filename); - } catch (Exception e) { - Log.Warning($"Error occurred while saving backup config to '{filename}': {e.ToString()}"); - } - Reset(conf); - } else { - Instance = conf; - ModifiedTime = WriteConfig(Instance); - } - } - - public static void Reset(GlobalConfig oldConfig, bool resetAll=false) { - Log.Info($"Resetting global config."); - DateTime modifiedTime; - Instance = WriteDefaultConfig(oldConfig, resetAll, out modifiedTime); - ModifiedTime = modifiedTime; - } - - private static void ReloadIfNewer() { - try { - DateTime modifiedTime = File.GetLastWriteTime(FILENAME); - if (modifiedTime > ModifiedTime) { - Log.Info($"Detected modification of global config."); - Reload(false); - } - } catch (Exception) { - Log.Warning("Could not determine modification date of global config."); - } - } - } -} + if (conf.AdvancedVehicleAI == null) { + conf.AdvancedVehicleAI = new AdvancedVehicleAI(); + } + + if (conf.DynamicLaneSelection == null) { + conf.DynamicLaneSelection = new DynamicLaneSelection(); + } + + if (conf.Gameplay == null) { + conf.Gameplay = new Gameplay(); + } + + if (conf.ParkingAI == null) { + conf.ParkingAI = new ParkingAI(); + } + + if (conf.PathFinding == null) { + conf.PathFinding = new PathFinding(); + } + + if (conf.PriorityRules == null) { + conf.PriorityRules = new PriorityRules(); + } + + if (conf.TimedTrafficLights == null) { + conf.TimedTrafficLights = new TimedTrafficLights(); + } + + return conf; + } + } catch (Exception e) { + Log.Warning($"Could not load global config: {e} Generating default config."); + return WriteDefaultConfig(null, false, out modifiedTime); + } + } + + public static void Reload(bool checkVersion=true) { + DateTime modifiedTime; + GlobalConfig conf = Load(out modifiedTime); + if (checkVersion && conf.Version != -1 && conf.Version < LATEST_VERSION) { + // backup old config and reset + string filename = BACKUP_FILENAME; + try { + int backupIndex = 0; + while (File.Exists(filename)) { + filename = BACKUP_FILENAME + "." + backupIndex; + ++backupIndex; + } + WriteConfig(conf, filename); + } catch (Exception e) { + Log.Warning($"Error occurred while saving backup config to '{filename}': {e.ToString()}"); + } + Reset(conf); + } else { + Instance = conf; + ModifiedTime = WriteConfig(Instance); + } + } + + public static void Reset(GlobalConfig oldConfig, bool resetAll=false) { + Log.Info($"Resetting global config."); + DateTime modifiedTime; + Instance = WriteDefaultConfig(oldConfig, resetAll, out modifiedTime); + ModifiedTime = modifiedTime; + } + + private static void ReloadIfNewer() { + try { + DateTime modifiedTime = File.GetLastWriteTime(FILENAME); + if (modifiedTime > ModifiedTime) { + Log.Info($"Detected modification of global config."); + Reload(false); + } + } catch (Exception) { + Log.Warning("Could not determine modification date of global config."); + } + } + } +} \ No newline at end of file From 2b179bb383d58edc0c33a09ad810ea274e5a682e Mon Sep 17 00:00:00 2001 From: Guy Fraser Date: Mon, 15 Jul 2019 19:37:32 +0100 Subject: [PATCH 142/142] Update lang_template.txt Added couple of missing keys --- TLM/TLM/Resources/lang_template.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/Resources/lang_template.txt b/TLM/TLM/Resources/lang_template.txt index 02004701a..ab9738a4b 100644 --- a/TLM/TLM/Resources/lang_template.txt +++ b/TLM/TLM/Resources/lang_template.txt @@ -232,6 +232,7 @@ Scan_for_known_incompatible_mods_on_startup Ignore_disabled_mods Traffic_Manager_detected_incompatible_mods Notify_me_if_there_is_an_unexpected_mod_conflict +Unsubscribe Keybind_toggle_TMPE_main_menu Keybind_toggle_traffic_lights_tool Keybind_use_lane_arrow_tool @@ -246,10 +247,11 @@ Keybind_Exit_subtool Keybind_conflict Keybind_category_Global Keybind_category_LaneConnector +Speed_limit_unlimited Display_speed_limits_mph Miles_per_hour Kilometers_per_hour Road_signs_theme_mph theme_Square_US theme_Round_UK -theme_Round_German +theme_Round_German \ No newline at end of file