diff --git a/TLM/TLM/Manager/Impl/OptionsManager.cs b/TLM/TLM/Manager/Impl/OptionsManager.cs index 7de7ad7f7..6126829b0 100644 --- a/TLM/TLM/Manager/Impl/OptionsManager.cs +++ b/TLM/TLM/Manager/Impl/OptionsManager.cs @@ -1,4 +1,4 @@ -namespace TrafficManager.Manager.Impl { +namespace TrafficManager.Manager.Impl { using CSUtil.Commons; using System; using TrafficManager.API.Manager; @@ -8,8 +8,7 @@ public class OptionsManager : AbstractCustomManager, - IOptionsManager - { + IOptionsManager { // TODO I contain ugly code public static OptionsManager Instance = new OptionsManager(); @@ -216,7 +215,7 @@ public bool LoadData(byte[] data) { OptionsVehicleRestrictionsTab.SetAddTrafficLightsIfApplicable(data[41] == 1); } - int LoadBool (int idx, ILegacySerializableOption opt) { + int LoadBool(int idx, ILegacySerializableOption opt) { if (data.Length > idx) { opt.Load(data[idx]); } @@ -236,6 +235,11 @@ int LoadBool (int idx, ILegacySerializableOption opt) { index = LoadBool(index, OptionsMassEditTab.PriorityRoad_EnterBlockedYeild); index = LoadBool(index, OptionsMassEditTab.PriorityRoad_StopAtEntry); + index = LoadBool(index, OptionsMassEditTab.RoundAboutQuickFix_KeepClearYieldR); + index = LoadBool(index, OptionsMassEditTab.RoundAboutQuickFix_RealisticSpeedLimits); + index = LoadBool(index, OptionsMassEditTab.RoundAboutQuickFix_ParkingBanMainR); + index = LoadBool(index, OptionsMassEditTab.RoundAboutQuickFix_ParkingBanYieldR); + return true; } @@ -295,6 +299,11 @@ public byte[] SaveData(ref bool success) { (byte)(OptionsMassEditTab.PriorityRoad_AllowLeftTurns.Save()), (byte)(OptionsMassEditTab.PriorityRoad_EnterBlockedYeild.Save()), (byte)(OptionsMassEditTab.PriorityRoad_StopAtEntry.Save()), + + (byte)(OptionsMassEditTab.RoundAboutQuickFix_KeepClearYieldR.Save()), + (byte)(OptionsMassEditTab.RoundAboutQuickFix_RealisticSpeedLimits.Save()), + (byte)(OptionsMassEditTab.RoundAboutQuickFix_ParkingBanMainR.Save()), + (byte)(OptionsMassEditTab.RoundAboutQuickFix_ParkingBanYieldR.Save()), }; } } diff --git a/TLM/TLM/Manager/Impl/ParkingRestrictionsManager.cs b/TLM/TLM/Manager/Impl/ParkingRestrictionsManager.cs index 57d8c1ad8..adef21ee2 100644 --- a/TLM/TLM/Manager/Impl/ParkingRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/ParkingRestrictionsManager.cs @@ -48,6 +48,17 @@ public bool ToggleParkingAllowed(ushort segmentId, NetInfo.Direction finalDir) { return SetParkingAllowed(segmentId, finalDir, !IsParkingAllowed(segmentId, finalDir)); } + /// + /// Sets Parking allowed to for all supported directions. + /// + /// falseif no there are no configurable lanes. + /// true if any parking rules were applied. + public bool SetParkingAllowed(ushort segmentId, bool flag) { + bool ret = SetParkingAllowed(segmentId, NetInfo.Direction.Forward, flag); + ret |= SetParkingAllowed(segmentId, NetInfo.Direction.Backward, flag); + return ret; + } + public bool SetParkingAllowed(ushort segmentId, NetInfo.Direction finalDir, bool flag) { #if DEBUG if (DebugSwitch.BasicParkingAILog.Get()) { diff --git a/TLM/TLM/Manager/Impl/SegmentEndManager.cs b/TLM/TLM/Manager/Impl/SegmentEndManager.cs index 1955ba464..4c6b6c869 100644 --- a/TLM/TLM/Manager/Impl/SegmentEndManager.cs +++ b/TLM/TLM/Manager/Impl/SegmentEndManager.cs @@ -1,4 +1,4 @@ -namespace TrafficManager.Manager.Impl { +namespace TrafficManager.Manager.Impl { using CSUtil.Commons; using System; using TrafficManager.API.Manager; @@ -6,6 +6,8 @@ using TrafficManager.State.ConfigData; using TrafficManager.Traffic.Impl; using TrafficManager.Traffic; + using CitiesGameBridge.Service; + using TrafficManager.Util; [Obsolete("should be removed when implementing issue #240")] public class SegmentEndManager @@ -42,6 +44,10 @@ public ISegmentEnd GetSegmentEnd(ushort segmentId, bool startNode) { return SegmentEnds[GetIndex(segmentId, startNode)]; } + internal ISegmentEnd GetSegmentEnd(int segmentEndIndex) { + return SegmentEnds[segmentEndIndex]; + } + public ISegmentEnd GetOrAddSegmentEnd(ISegmentEndId endId) { return GetOrAddSegmentEnd(endId.SegmentId, endId.StartNode); } @@ -155,10 +161,16 @@ public bool UpdateSegmentEnd(ushort segmentId, bool startNode) { } } - private int GetIndex(ushort segmentId, bool startNode) { + internal int GetIndex(ushort segmentId, bool startNode) { return segmentId + (startNode ? 0 : NetManager.MAX_SEGMENT_COUNT); } + internal void GetSegmentAndNodeFromIndex(int index, out ushort segmentId, out bool startNode) { + Shortcuts.Assert(index < 2 * NetManager.MAX_SEGMENT_COUNT && index > 0); + startNode = index < NetManager.MAX_SEGMENT_COUNT; + segmentId = (ushort)(index - (startNode ? 0 : NetManager.MAX_SEGMENT_COUNT)); + } + // protected override void HandleInvalidSegment(SegmentGeometry geometry) { // RemoveSegmentEnds(geometry.SegmentId); // } diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index 1b238a73d..35b12b96e 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -575,6 +575,19 @@ public bool SetSpeedLimit(ushort segmentId, return true; } + /// + /// Sets speed limit for all configurable lanes. + /// + /// Speed limit in game units, or null to restore defaults + /// falseif there are no configurable lanes. true if any speed limits were applied. + public bool SetSpeedLimit(ushort segmentId, float? speedLimit) { + bool ret = false; + foreach(NetInfo.Direction finaldir in Enum.GetValues(typeof(NetInfo.Direction))) { + ret |= SetSpeedLimit(segmentId, finaldir, speedLimit); + } + return ret; + } + /// /// Sets the speed limit of a given segment and lane direction. /// diff --git a/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs b/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs index 6e0adca36..3e6c4cf49 100644 --- a/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs @@ -11,6 +11,7 @@ namespace TrafficManager.Manager.Impl { using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Util; + using GenericGameBridge.Service; public class VehicleRestrictionsManager : AbstractGeometryObservingManager, @@ -352,6 +353,38 @@ internal ExtVehicleType GetDefaultAllowedVehicleTypes( return ExtVehicleType.None; } + /// + /// Restores all vehicle restrictions on a given segment to default state. + /// + /// + /// + internal bool ClearVehicleRestrictions(ushort segmentId) { + NetInfo segmentInfo = segmentId.ToSegment().Info; + bool ret = false; + IList lanes = Constants.ServiceFactory.NetService.GetSortedLanes( + segmentId, + ref segmentId.ToSegment(), + null, + LANE_TYPES, + VEHICLE_TYPES, + sort: false); + + foreach (LanePos laneData in lanes) { + uint laneId = laneData.laneId; + byte laneIndex = laneData.laneIndex; + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + + ret |= SetAllowedVehicleTypes( + segmentId, + segmentInfo, + laneIndex, + laneInfo, + laneId, + EXT_VEHICLE_TYPES); + } + return ret; + } + /// /// Sets the allowed vehicle types for the given segment and lane. /// diff --git a/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs b/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs index 7c97ce993..e0b41bd86 100644 --- a/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs +++ b/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs @@ -3,6 +3,7 @@ namespace TrafficManager.Patch._RoadWorldInfoPanel using ColossalFramework; using Harmony; using JetBrains.Annotations; + using TrafficManager.UI; [HarmonyPatch(typeof(RoadWorldInfoPanel), "OnAdjustRoadButton")] [UsedImplicitly] diff --git a/TLM/TLM/Resources/Translations/Menu.csv b/TLM/TLM/Resources/Translations/Menu.csv index 1d557d33b..8e8bac4f2 100644 --- a/TLM/TLM/Resources/Translations/Menu.csv +++ b/TLM/TLM/Resources/Translations/Menu.csv @@ -18,14 +18,6 @@ "Tooltip:Lane connector","Fahrspurverbinder","Lane Connector","Conector de carriles","Connecteur de voies","Sávcsatlakozó","Connettore di corsia","車線を接続","차선 연결","Rijstrookverbinder","Połącz pasy ruchu","Conector de faixas","Соединения Полос Движения","車道連接工具","车道连接","Lane connector","Şerit birleştiricisi","З'єднання Смуг Руху","Připojení jízdních pruhů","","" "Tooltip:Junction restrictions","Kreuzungsbeschränkungen","Junction Restrictions","Restricciones del cruce","Restrictions des intersections","Csomópont korlátozások","Restrizioni d'incrocio","交差点の制限","교차로 규칙","Splitsingsbeperkingen","Ograniczenia na skrzyżowaniach","Restrições de junção","Ограничения на Перекрёстках","交岔路口動線連接制定","路口管制","Junction restrictions","Kavşak kısıtlamaları","Обмеження на Перехресті","Pravidla křižovatky","","" "Tooltip.Keybinds:Auto TL","Strg+Klick: Automatische Einstellung der Ampeln","Ctrl+Click: Quick setup","Semáforos automáticos","Feux automatiques","Automatikus jelzőlámpa","Ctrl+Clic: Configurazione rapida","Ctrl+クリック: クイックセットアップ","Ctrl+클릭: 빠른 설정","Ctrl+Shift+Klik: Automatische setup van de verkeerslichten","Ctrl + Klik: Szybka instalacja","Ctrl+Clique: Configuração rápida","Ctrl+Click: Автоматическое создание светофора с таймером","自動化號誌","Ctrl+点击: 快速设置","Ctrl+Click: Quick setup","Özdevinimli TI","Ctrl+Click: Автоматичне створення світлофора з таймером","Ctrl + Levý klik: rychlé nastavení semaforu","","" -"Roundabout.Option:Allocate dedicated exit lanes","Dedizierte Ausfahrspuren zuweisen","Allocate dedicated exit lanes","","Attribuer des voies de sortie dédiées","","Assegna corsie di uscita","専用の出口レーンを割り当てる","출구 전용 차선 할당","Wijs speciale exit-rijstroken toe","Przydziel dedykowane pasy zjazdu z ronda","Atribuir faixas de saída dedicadas","Выделенные полосы для покидания круга","分配出口專用車道","","Allocate dedicated exit lanes","Ayrılmış çıkış şeritleri özgüle","Окремі смуги для виїзду з кругового перехрестя","Povolit samostatné pruhy pro výjezd","","" -"Roundabout.Tooltip:Allocate dedicated exit lanes","Dedizierte Ausfahrspuren zuweisen","One dedicated lane for each exit, the rest of lanes go straight","","Attribuer des voies de sortie dédiées","","Una corsia per ogni uscita, le restanti per proseguire dritto","各出口に1つの専用レーン、残りのレーンは直進","각 출구에 대해 전용 차선 1개, 나머지 차선은 직진","Een speciale rijstrook voor elke afslag, de rest van de rijstroken gaat rechtdoor","Dedykowany pas do zjazdu, pozostałe do jazdy na wprost","Apenas uma das faixas será dedicada para cada saída, o resto seguirá em frente","Крайние полосы будут назначены исключительно для съезда с круга","分配出口專用車道","","One dedicated lane for each exit, the rest of lanes go straight","Her çıkışa bir şerit özgüler, geriye kalan şeritler düz akar","Призначити крайні смуги руху виключно для виїзду з кругового перехрестя","Přiřadí vyhrazené pruhy pro každý výjezd, ostatní budou pokračovat rovně","","" -"Roundabout.Option:Stay in lane inside roundabout","Keine Spurwechsel im Kreisverkehr","Stay in lane inside the roundabout","","Rester dans la voie à l'intérieur du rond-point","Maradjon a sávjában a körforgalomban","Nella rotonda i veicoli devono mantenere la corsia","ラウンドアバウト交差点内では車線にとどまる","라운드어바웃 안에서의 차선 유지","Blijf in lijn binnen de rotonde","'Pozostań na pasie' na drogach wewnątrz ronda","Permanecer na faixa dentro da rotatória","Оставаться в полосе внутри круга","將車道留在圓環島內","","Stay in lane inside the roundabout","Dönel kavşak içinde şerit değiştirilmesin","Тримати смугу всередині кругового перехрестя","Zůstat v pruhu během jízdy v kruhovém objezdu","","" -"Roundabout.Option:Stay in lane outside roundabout","Keine Spurwechsel außerhalb eines Kreisverkers","Stay in lane on the roads approaching the roundabout","","Rester dans la voie à l'extérieur du rond-point","Sávon belül maradjon a körforgalmon kívül","Nelle corsie in avvicinamento alla rotonda i veicoli devono mantenere la corsia","ラウンドアバウト交差点に近づく道路で車線に留まる","라운드어바웃으로 접근하는 도로를 차선 유지","Blijf in de lijn op wegen die de rotonde naderen","'Pozostań na pasie' na drogach dojazdowych do ronda","Permanecer na faixa nas ruas de acesso à rotatória","Оставаться в полосе на подъезде к кругу","將車道留在圓環島外","","Stay in lane on the roads approaching the roundabout","Dönel kavşak bağlantılarında şerit değiştirilmesin","Тримати смугу на під'їзді до кругового перехрестя","Zůstat v pruhu opouštění kruhového objezdu","","" -"Roundabout.Tooltip:Stay in lane outside roundabout","Fahrzeuge vor einem Kreisverkehr wechseln nicht die Spur","Vehicles shall not switch lanes too close to the roundabout","","Rester dans la voie à l'extérieur du rond-point","Maradjon a sávjában a körforgalmon kívül","I veicoli non possono cambiare corsia troppo vicini alla rotonda","車両は、ラウンドアバウト交差点に接近して車線を変更してはならない","차량은 차선을 라운드어바웃에 너무 가깝게 전환하지 않는다","Voertuigen mogen niet van rijstrook wisselen die te dicht bij de rotonde liggen","Pojazdy nie powinny zmieniać pasa zbyt blisko ronda","Proibir a mudança de faixas de veículos muito próximos a rotatória","Если узел дороги находится слишком близко к кругу, будет назначено правило ""оставаться в полосе""","將車道留在圓環島外","","Vehicles shall not switch lanes too close to the roundabout","Taşıtlar dönel kavşağa yaklaşırken şerit değiştirmesin","Заборонити зміну смуг руху, якщо вузол знаходиться близько до кругового перехрестя","Vozidla nesmí měnit jízdní pruhy příliš blízko kruhovému objezdu","","" -"Roundabout.Option:No crossing inside","Fußgänger betreten nicht die Mitte eines Kreisels","Pedestrians shall not cross to the center of roundabout","","Pas de croisement à l'intérieur","Ne legyen gyalogátkelő a körforgalomban","I pedoni non possono attraversare al centro della rotonda","歩行者は、ラウンドアバウト交差点の中心部に進入してはならない。","라운드어바웃의 중앙으로 보행자가 건너지 않음","Voetgangers mogen de oversteekplaatsen niet oversteken die op de rotonde liggen","Pieszy nie powinni przechodzić przez drogę do środka ronda","Proibir a travessia de pedestres para o centro da rotatória","Запретить пешеходные переходы внутри круга","內部無路口","","Pedestrians shall not cross to the centre of the roundabout","Yayalar kavşak ortasından karşıya geçmesin","Заборонити пішохідні переходи всередині","Chodci by nesmí přecházet kruhový objezd doprostřed","","" -"Roundabout.Option:No crossing on incoming roads","Keine Überquerung über ankommende Straßen","Pedestrians shall not cross the roads approaching the roundabout","","Pas de croisement sur les routes entrantes","Ne legyen gyalogátkelő a bejövő utakon","I pedoni non possono attraversare le strade in avvicinamento alla rotonda","歩行者は、ラウンドアバウト交差点に進入する道路を横断してはならない","라운드어바웃으로 접근하는 도로를 보행자가 건너지 않음","Voetgangers mogen de weg niet oversteken die aan de inkomende wegen van de rotonde liggen","Pieszy nie powinni przechodzić przez drogę tuż przed rondem","Proibir a travessia de pedestres nas ruas de acesso a rotatória","Запретить пешеходные переходы на подъездных дорогах к кругу","路口無連接的道路","","Pedestrians shall not cross the roads approaching the roundabout","Yayalar kavşak bağlantılarına yakınken karşıya geçmesin","Заборонити пішохідні переході на під'їздних дорогах","Chodci nesmí přecházet silnici blízko kruhovému objezdu","","" -"Roundabout.Option:Set priority signs","Vorfahrtsschilder setzen","Add priority signs on the roundabout junction","Añadir señales de prioridad en los cruces de la rotonda","Définir les panneaux de priorité","Elsőbbségadás tábla hozzáadása a körforgalomhoz","Aggiungi segnali di precedenza alla rotonda","ラウンドアバウト交差点に優先標識を追加する","라운드어바웃 교차로에 우선주행 표지판 추가","Voeg Prioriteitsborden toe aan rotondes","Dodaj znaki pierwszeństwa przejazdu na rondzie","Adicionar placas de prioridade na junção da rotatória","Установить знаки приоритета","設定優先標誌","","Add priority signs on the roundabout junction","Öncelik işaretlerini ayarla","Встановити знаки пріоритету на круговому перехресті","Přidat přednost na kruhovém objezdu","","" "Tooltip:Disable despawning","Despawn ausschalten","Toggle automatic vehicle despawning Currently: ENABLED (easy mode, less traffic jams)","Desactivar desaparición","Désactiver la disparition","Letiltja a hajtást","Disabilita la sparizione dei veicoli","スタック除去無効化","차 사라짐 비활성화","Voertuigverdwijning uitschakelen","Zablokuj znikanie pojazdów","Desativar desaparecimento de carros","Переключение режима Исчезновения Транспорта\n Сейчас: Включено (лёгкий режим, пробки случаются реже)","久塞車輛永不消失","禁止车辆消失","Despawning is currently enabled. Disable despawning.","Yok olmayı devre dışı bırak","Переключення режиму Зникнення Застряглих Автівок\n diff --git a/TLM/TLM/Resources/Translations/Options.csv b/TLM/TLM/Resources/Translations/Options.csv index 94afdfe41..e4f8f2032 100644 --- a/TLM/TLM/Resources/Translations/Options.csv +++ b/TLM/TLM/Resources/Translations/Options.csv @@ -99,6 +99,14 @@ "VR.Checkbox:Automatically add traffic lights if applicable","Wo möglich, Ampeln automatisch hinzufügen","Automatically add traffic lights if applicable","","Ajouter automatiquement les feux de signalisation si possible","Automatikusan adjon hozzá jelzőlámpákat, ha lehetséges","Aggiungi i semafori automaticamente se possibile","該当する場合、信号を自動的に追加する","해당되는 경우 자동으로 신호등 추가","Voeg automatisch verkeerslicht toe indien van toepassing","Automatycznie dodaj sygnalizację świetlną dla nowych skrzyżowań","Automaticamente adicionar semáforos se possível","Добавлять светофоры автоматически, где это применимо","如果允許,自動加入號誌燈","在合适的情况下自动增加红绿灯","Automatically add traffic lights if applicable","Eğer uygunsa trafik ışıklarını kendiliğinden ekle","Автоматично додавати світлофор, якщо це має сенс","Automaticky zapni semafory pokud je to možno","","" "MassEdit.Group:Roundabouts","Kreisverkehr","Roundabouts","Rotonda","Ronds-points","Körforgalmak","Rotatorie","ラウンドアバウト","라운드어바웃","MassEdit.Group:Rotondes","Ronda","Rotatórias","Движение по кругу","圓環島","环岛","Roundabouts","Dönel kavşaklar","Круговий рух","Kruhový objezd","","" "MassEdit.Group.Priority roads","Prioritätsstraßen","Priority roads","","Priorité des routes","Felsőbbrendű utak","Strade prioritarie","優先道路","우선 주행 도로","Prioritaire wegen","Drogi priorytetowe","Ruas de prioridade","Главная дорога","優先道路","","Priority roads","Öncelikli yollar","Головна дорога","Hlavní silnice","","" +"Roundabout.Option:Allocate dedicated exit lanes","Dedizierte Ausfahrspuren zuweisen","Allocate dedicated exit lanes","","Attribuer des voies de sortie dédiées","","Assegna corsie di uscita","専用の出口レーンを割り当てる","출구 전용 차선 할당","Wijs speciale exit-rijstroken toe","Przydziel dedykowane pasy zjazdu z ronda","Atribuir faixas de saída dedicadas","Выделенные полосы для покидания круга","分配出口專用車道","","Allocate dedicated exit lanes","Ayrılmış çıkış şeritleri özgüle","Окремі смуги для виїзду з кругового перехрестя","Povolit samostatné pruhy pro výjezd","","" +"Roundabout.Tooltip:Allocate dedicated exit lanes","Dedizierte Ausfahrspuren zuweisen","One dedicated lane for each exit, the rest of lanes go straight","","Attribuer des voies de sortie dédiées","","Una corsia per ogni uscita, le restanti per proseguire dritto","各出口に1つの専用レーン、残りのレーンは直進","각 출구에 대해 전용 차선 1개, 나머지 차선은 직진","Een speciale rijstrook voor elke afslag, de rest van de rijstroken gaat rechtdoor","Dedykowany pas do zjazdu, pozostałe do jazdy na wprost","Apenas uma das faixas será dedicada para cada saída, o resto seguirá em frente","Крайние полосы будут назначены исключительно для съезда с круга","分配出口專用車道","","One dedicated lane for each exit, the rest of lanes go straight","Her çıkışa bir şerit özgüler, geriye kalan şeritler düz akar","Призначити крайні смуги руху виключно для виїзду з кругового перехрестя","Přiřadí vyhrazené pruhy pro každý výjezd, ostatní budou pokračovat rovně","","" +"Roundabout.Option:Stay in lane inside roundabout","Keine Spurwechsel im Kreisverkehr","Stay in lane inside the roundabout","","Rester dans la voie à l'intérieur du rond-point","Maradjon a sávjában a körforgalomban","Nella rotonda i veicoli devono mantenere la corsia","ラウンドアバウト交差点内では車線にとどまる","라운드어바웃 안에서의 차선 유지","Blijf in lijn binnen de rotonde","'Pozostań na pasie' na drogach wewnątrz ronda","Permanecer na faixa dentro da rotatória","Оставаться в полосе внутри круга","將車道留在圓環島內","","Stay in lane inside the roundabout","Dönel kavşak içinde şerit değiştirilmesin","Тримати смугу всередині кругового перехрестя","Zůstat v pruhu během jízdy v kruhovém objezdu","","" +"Roundabout.Option:Stay in lane outside roundabout","Keine Spurwechsel außerhalb eines Kreisverkers","Stay in lane on the roads approaching the roundabout","","Rester dans la voie à l'extérieur du rond-point","Sávon belül maradjon a körforgalmon kívül","Nelle corsie in avvicinamento alla rotonda i veicoli devono mantenere la corsia","ラウンドアバウト交差点に近づく道路で車線に留まる","라운드어바웃으로 접근하는 도로를 차선 유지","Blijf in de lijn op wegen die de rotonde naderen","'Pozostań na pasie' na drogach dojazdowych do ronda","Permanecer na faixa nas ruas de acesso à rotatória","Оставаться в полосе на подъезде к кругу","將車道留在圓環島外","","Stay in lane on the roads approaching the roundabout","Dönel kavşak bağlantılarında şerit değiştirilmesin","Тримати смугу на під'їзді до кругового перехрестя","Zůstat v pruhu opouštění kruhového objezdu","","" +"Roundabout.Tooltip:Stay in lane outside roundabout","Fahrzeuge vor einem Kreisverkehr wechseln nicht die Spur","Vehicles shall not switch lanes too close to the roundabout","","Rester dans la voie à l'extérieur du rond-point","Maradjon a sávjában a körforgalmon kívül","I veicoli non possono cambiare corsia troppo vicini alla rotonda","車両は、ラウンドアバウト交差点に接近して車線を変更してはならない","차량은 차선을 라운드어바웃에 너무 가깝게 전환하지 않는다","Voertuigen mogen niet van rijstrook wisselen die te dicht bij de rotonde liggen","Pojazdy nie powinny zmieniać pasa zbyt blisko ronda","Proibir a mudança de faixas de veículos muito próximos a rotatória","Если узел дороги находится слишком близко к кругу, будет назначено правило ""оставаться в полосе""","將車道留在圓環島外","","Vehicles shall not switch lanes too close to the roundabout","Taşıtlar dönel kavşağa yaklaşırken şerit değiştirmesin","Заборонити зміну смуг руху, якщо вузол знаходиться близько до кругового перехрестя","Vozidla nesmí měnit jízdní pruhy příliš blízko kruhovému objezdu","","" +"Roundabout.Option:No crossing inside","Fußgänger betreten nicht die Mitte eines Kreisels","Pedestrians shall not cross to the center of roundabout","","Pas de croisement à l'intérieur","Ne legyen gyalogátkelő a körforgalomban","I pedoni non possono attraversare al centro della rotonda","歩行者は、ラウンドアバウト交差点の中心部に進入してはならない。","라운드어바웃의 중앙으로 보행자가 건너지 않음","Voetgangers mogen de oversteekplaatsen niet oversteken die op de rotonde liggen","Pieszy nie powinni przechodzić przez drogę do środka ronda","Proibir a travessia de pedestres para o centro da rotatória","Запретить пешеходные переходы внутри круга","內部無路口","","Pedestrians shall not cross to the centre of the roundabout","Yayalar kavşak ortasından karşıya geçmesin","Заборонити пішохідні переходи всередині","Chodci by nesmí přecházet kruhový objezd doprostřed","","" +"Roundabout.Option:No crossing on incoming roads","Keine Überquerung über ankommende Straßen","Pedestrians shall not cross the roads approaching the roundabout","","Pas de croisement sur les routes entrantes","Ne legyen gyalogátkelő a bejövő utakon","I pedoni non possono attraversare le strade in avvicinamento alla rotonda","歩行者は、ラウンドアバウト交差点に進入する道路を横断してはならない","라운드어바웃으로 접근하는 도로를 보행자가 건너지 않음","Voetgangers mogen de weg niet oversteken die aan de inkomende wegen van de rotonde liggen","Pieszy nie powinni przechodzić przez drogę tuż przed rondem","Proibir a travessia de pedestres nas ruas de acesso a rotatória","Запретить пешеходные переходы на подъездных дорогах к кругу","路口無連接的道路","","Pedestrians shall not cross the roads approaching the roundabout","Yayalar kavşak bağlantılarına yakınken karşıya geçmesin","Заборонити пішохідні переході на під'їздних дорогах","Chodci nesmí přecházet silnici blízko kruhovému objezdu","","" +"Roundabout.Option:Set priority signs","Vorfahrtsschilder setzen","Add priority signs on the roundabout junction","Añadir señales de prioridad en los cruces de la rotonda","Définir les panneaux de priorité","Elsőbbségadás tábla hozzáadása a körforgalomhoz","Aggiungi segnali di precedenza alla rotonda","ラウンドアバウト交差点に優先標識を追加する","라운드어바웃 교차로에 우선주행 표지판 추가","Voeg Prioriteitsborden toe aan rotondes","Dodaj znaki pierwszeństwa przejazdu na rondzie","Adicionar placas de prioridade na junção da rotatória","Установить знаки приоритета","設定優先標誌","","Add priority signs on the roundabout junction","Öncelik işaretlerini ayarla","Встановити знаки пріоритету на круговому перехресті","Přidat přednost na kruhovém objezdu","","" "Priority roads.Option:Allow far turns","Erlaube Fahrzeugen, von einer Straße über eine Spur abzubiegen/auf die Hauptstraße zu fahren (nicht empfohlen)","Allow cars to take far turn from/into main road (not recommended)","","Option : Autoriser les virages lointains/éloignés","","Permette alle auto di svoltare lontano da/verso la strada principale (non raccomandato)","自動車の優先道路からの分流/合流を許可します(非推奨)","차량 중앙차선으로 회전 허용 (권장하지 않음)","Sta auto's toe ver af te slaan van / naar hoofdweg (niet aanbevolen)","Zezwól na wykonanie skrętu z przecięciem przeciwległej jezdni","Permitir que carros escolham uma curva mais distante de/para a rua principal (não recomendado)","Позволить повороты через встречную полосу","允許車輛可逕行從幹道轉入/轉入至幹道(不建議)","","Allow cars to take far turn from/into main road (not recommended)","Geniş dönüşlere izin ver","Дозволити поворот через смугу зустрічного руху","Povolit vzdálené otáčení vozidel z/na hlavní silnici (nedoporučujeme)","","" "Priority roads.Tooltip:Allow far turns","Wenn diese Option aktiviert ist, werden die Fahrspurpfeile nicht geändert, und Autos können den Mittelpunkt der Kreuzung überqueren (gilt nicht, wenn sich die Allee teilt)","If enabled, lane arrows are untouched and cars can cross road median (does not apply when splitting avenue).","","Info-bulle : Autoriser les virages lointains/éloignés","","Se abilitato, le frecce di corsia non verranno modificate e le auto possono attraversare il punto medio dell'incrocio (non si applica quando il viale si divide)","有効にすると、車線の矢印はそのままになり、車は道路の中央分離帯を横切ることができます(道を分割する場合は適用されません)。","활성화되면, 차선을 무시하고 차량이 도로 중앙으로 진입이 가능해집니다. (분할된 에비뉴 도로 미적용됨)","Indien ingeschakeld, blijven rijstrookpijlen onaangeroerd en kunnen auto's de mediaan van de weg oversteken (niet van toepassing bij het splitsen van de rijbaan).","Jeśli aktywne, strzałki pasów pozostają nietknięte a pojazdy mogą przecinać przeciwległą jezdnię","Caso ativado, as setas de faixa não serão modificadas e os carros poderão cruzar a mediana da rua (não se aplica ao dividir avenida).","Повороты через встречную полосу без разделителя полос","如果啟用此選項,車道箭頭將不改變,且車輛可逕行穿越道路中央(不適用含有分隔島的道路)","","If enabled, lane arrows are untouched and cars can cross road median (does not apply when splitting avenue).","Geniş dönüşlere izin ver","Дозволити повороти через зустрічний рух, якщо немає розділювача смуг","Pokud je zapnuto, jízdní pruhy nejsou dotčeny a automobily mohou pokračovat","","" "Priority roads.Option:Enter blocked yield road","Erlauben den Fahrzeugen auf der Ausweichstraße die Einfahrt in eine blockierte Hauptstraße","Allow cars on yield road to enter blocked main road","","Option : Entrer sur la route même bloquée","Elsőbbségadás kötelező táblánál az autók továbbhajthatnak","Consente alle auto su strade con obbligo di dare la precedenza di entrare su strade principali bloccate","高速道路上の車を通行止めの優先道路に進入させる","양보 도로에서 주행 중인 차량이 막힌 메인 도로로 진입할 수 있도록 허용","Sta auto's toe om de geblokkeerde hoofdweg op te rijden vanuit de wegen die de hoofdweg voorrang moeten geven","Zezwól na wjazd z podporządkowanej na zablokowaną drogę z pierwszeństwem","Permitir que carros em espera entrem na rua principal bloqueada","Разрешить въезд на главную дорогу, занятую другими авто","允許支道車輛進入阻塞的幹道","","Allow cars on yield road to enter blocked main road","","Дозволити в'їзд на головну дорогу якщо місце зайнято іншими авто","Vozidla mohou na značce ""dej přednost"" vjet do zaplněné křižovatky","","" @@ -145,3 +153,13 @@ Fichier: TM: PE - Options du Mod","","","","","Verlaat segment","Odznacz wybrany "Keybind.RightClick:Leave lane","","Leave selected lane","","LienClé. Clic droit : Quitter la voie de circulation ou d'intersection Laisser la voie de circulation ou d'intersection sélectionnée Fichier: TM: PE - Options du Mod","","","選択した車線をそのままにする","","","Odznacz wybrany pas","","Покинуть режим редактирования полосы","","","Leave selected lane","","Залишити обрану смугу руху","Opustit pruh","","" +"Roundabout.Option:Yielding vehicles keep clear of blocked roundabout","Kreisverkehr freihalten und Vorfahrt gewähren","Yielding vehicles keep clear of blocked roundabout","","Rond-point. Option : Au rond-point, restez à l'écart pour céder le passage","","","非優先道路の迂回路を避ける","","","","","","","","Yielding vehicles keep clear of blocked roundabout","","","","","" +"Roundabout.Tooltip:Yielding vehicles keep clear of blocked roundabout","Der Einfahrtsbereich in den Kreisverkehr wird freigehalten und die Fahrzeuge im Kreisverkehr haben Vorfahrt","If enabled, cars entering the roundabout will not enter blocked roundabout to avoid getting in the way of cars inside of the roundabout. +If disabled, traffic rule is untouched","","","","","","","","","","","","","If enabled, cars entering the roundabout will not enter roundabout when blocked to avoid getting in the way of cars inside of the roundabout. +If disabled, traffic rule is untouched","","","","","" +"Roundabout.Option:Assign realistic speed limits to roundabouts","","Assign realistic speed limits to roundabouts","","","","","","","","","","","","","Assign realistic speed limits to roundabouts","","","","","" +"Roundabout.Tooltip:Assign realistic speed limits to roundabouts","","reducing roundabout speed might reduce risk of accident in real word but does not boost traffic flow in this game.\n +This option is only for the purpose of adding realism to the game. ","","","","","","","","","","","","","reducing roundabout speed might reduce risk of accident in real word but does not boost traffic flow in this game.\n +This option is only for the purpose of adding realism to the game. ","","","","","" +"Roundabout.Option:Put parking ban inside roundabouts","","Put parking ban inside roundabouts","","","","","","","","","","","","","Put parking ban inside roundabouts","","","","","" +"Roundabout.Option:Put parking ban on roundabout branches","","Put parking bans on roundabout branches","","","","","","","","","","","","","Put parking bans on roundabout branches","","","","","" diff --git a/TLM/TLM/Resources/Translations/PrioritySigns.csv b/TLM/TLM/Resources/Translations/PrioritySigns.csv index 1f71b8293..5080e4e81 100644 --- a/TLM/TLM/Resources/Translations/PrioritySigns.csv +++ b/TLM/TLM/Resources/Translations/PrioritySigns.csv @@ -5,12 +5,12 @@ Fichier : TM: PE - Outil - Signes prioritaires","","","","","","Wybierz skrzyżo "Prio.OnscreenHint.Mode:Edit","Bearbeiten","Click circles or signs to toggle","","Priorité. Astuce à l'écran. Mode : Modifier S'affiche lorsque l'outil des Signes Prioritaires est actif et qu'un nœud, un carrefour ou une jonction est sélectionné, suggérant que l'utilisateur peut désormais modifier les signes prioritaires Fichier : TM: PE - Outil - Signes prioritaires","","","","","","Kliknij w wybrany okrąg lub znak aby zmieniać ustawienie","","Нажимайте на значки приоритета для переключения","","","Click circles or signs to toggle","","Натискайте знаки приорітету для внесення змін","Zobrazí se, když je aktivní nástroj Prio Signs a je vybrán nějaký uzel, což naznačuje, že uživatel může nyní upravovat značky","","" -"Prio.Click:Quick setup prio road/roundabout","Vorfahrtsstraße/Kreisverkehr schnell einrichten","Quick setup priority road/roundabout","","Priorité. Cliquez sur : Route/Rond-point prioritaire à configuration rapide +"Prio.Click:Quick setup prio road/roundabout","Vorfahrtsstraße/Kreisverkehr schnell einrichten","Bulk-edit priority signs on a road/roundabout","","Priorité. Cliquez sur : Route/Rond-point prioritaire à configuration rapide Cliquez sur l'action de l'outil de signalisation prioritaire : Configuration rapide route/rond-point prioritaire -Fichier : TM: PE - Outil - Signes prioritaires","","","","","Snelle setup voor prioritaire wegen/rotondes","Szybka konfiguracja drogi/ronda","","Быстрая настройка приоритетной дороги или круга","","","Quick setup priority road/roundabout","","Швидке створення пріоритетної дороги або кола","Rychlé nastavení hlavní silnice / kruhového objezdu","","" +Fichier : TM: PE - Outil - Signes prioritaires","","","クイックセットアップ 優先道路/ラウンドアバウト","","Snelle setup voor prioritaire wegen/rotondes","Szybka konfiguracja drogi/ronda","","Быстрая настройка приоритетной дороги или круга","","","Bulk-edit priority signs on a road/roundabout","","Швидке створення пріоритетної дороги або кола","Rychlé nastavení hlavní silnice / kruhového objezdu","","" "Prio.Click:Quick setup high prio road/roundabout","Vorfahrtsstraße/Kreisverkehr mit hoher Priorität schnell einrichten","Quick setup high priority road/roundabout","","Priorité. Cliquez : Configuration rapide route/rond-point à circulation dense Cliquez sur l'action sur la jonction dans l'outil Signalisation prioritaire: Configuration rapide de route prioritaire ou de rond-point -Fichier : TM: PE - Outil - Signes prioritaires","","","","","Snelle setup voor hoog prioritaire wegen/rotondes","Szybka konfiguracja drogi/ronda o wysokim priorytecie","","Быстрая настройка высокоприоритетной дороги/круга","","","Quick setup high priority road/roundabout","","Швидке створення високоприорітетної дороги/кола","Rychlé nastavení vysoké prioritní silnice / kruhového objezdu","","" -"Prio.Click:Quick setup prio junction","Vorfahrtsregeln an der Kreuzung schnell einrichten","Quick setup priority junction","","Priorité. Cliquez sur : Jonction prioritaire de configuration rapide de route/rond-point +Fichier : TM: PE - Outil - Signes prioritaires","","","クイックセットアップ 高優先道路/ラウンドアバウト","","Snelle setup voor hoog prioritaire wegen/rotondes","Szybka konfiguracja drogi/ronda o wysokim priorytecie","","Быстрая настройка высокоприоритетной дороги/круга","","","Quick setup high priority road/roundabout","","Швидке створення високоприорітетної дороги/кола","Rychlé nastavení vysoké prioritní silnice / kruhového objezdu","","" +"Prio.Click:Quick setup prio junction","Vorfahrtsregeln an der Kreuzung schnell einrichten","Quick setup high priority junction","","Priorité. Cliquez sur : Jonction prioritaire de configuration rapide de route/rond-point Cliquez sur l'action dans l'outil Signes prioritaires : configuration rapide d'une jonction prioritaire -Fichier : TM: PE - Outil - Signes prioritaires","","","","","Snelle setup voor een prioriteits-knooppunt","Szybka konfiguracja skrzyżowania z priorytetem ruchu","","Быстрая настройка приоритетного перекрёстка","","","Quick setup priority junction","","Створення приорітетного перехрестя","Rychlé nastavení hlavní silnice","","" +Fichier : TM: PE - Outil - Signes prioritaires","","","クイックセットアップ 優先ジャンクション","","Snelle setup voor een prioriteits-knooppunt","Szybka konfiguracja skrzyżowania z priorytetem ruchu","","Быстрая настройка приоритетного перекрёстка","","","Quick setup high priority junction","","Створення приорітетного перехрестя","Rychlé nastavení hlavní silnice","","" diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index 4562fe995..88aa75074 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -101,6 +101,10 @@ public class Options : MonoBehaviour { public static bool RoundAboutQuickFix_NoCrossMainR = true; public static bool RoundAboutQuickFix_NoCrossYieldR = false; public static bool RoundAboutQuickFix_PrioritySigns = true; + public static bool RoundAboutQuickFix_KeepClearYieldR = true; + public static bool RoundAboutQuickFix_RealisticSpeedLimits = false; + public static bool RoundAboutQuickFix_ParkingBanMainR = true; + public static bool RoundAboutQuickFix_ParkingBanYieldR = false; public static bool PriorityRoad_CrossMainR = false; public static bool PriorityRoad_AllowLeftTurns = false; public static bool PriorityRoad_EnterBlockedYeild = false; diff --git a/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs b/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs index b3182ab03..2a662f60b 100644 --- a/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs +++ b/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs @@ -4,6 +4,7 @@ namespace TrafficManager.State { using TrafficManager.UI; public static class OptionsMassEditTab { + #region Roundabout options public static CheckboxOption RoundAboutQuickFix_DedicatedExitLanes = new CheckboxOption("RoundAboutQuickFix_DedicatedExitLanes") { Label = "Roundabout.Option:Allocate dedicated exit lanes", @@ -36,6 +37,30 @@ public static class OptionsMassEditTab { Label = "Roundabout.Option:Set priority signs", }; + public static CheckboxOption RoundAboutQuickFix_KeepClearYieldR = + new CheckboxOption("RoundAboutQuickFix_KeepClearYieldR") { + Label = "Roundabout.Option:Yielding vehicles keep clear of blocked roundabout", + Tooltip = "Roundabout.Tooltip:Yielding vehicles keep clear of blocked roundabout", + }; + + public static CheckboxOption RoundAboutQuickFix_RealisticSpeedLimits = + new CheckboxOption("RoundAboutQuickFix_RealisticSpeedLimits") { + Label = "Roundabout.Option:Assign realistic speed limits to roundabouts", + Tooltip = "Roundabout.Tooltip:Assign realistic speed limits to roundabouts", + }; + + public static CheckboxOption RoundAboutQuickFix_ParkingBanMainR = + new CheckboxOption("RoundAboutQuickFix_ParkingBanMainR") { + Label = "Roundabout.Option:Put parking ban inside roundabouts", + }; + + public static CheckboxOption RoundAboutQuickFix_ParkingBanYieldR = + new CheckboxOption("RoundAboutQuickFix_ParkingBanYieldR") { + Label = "Roundabout.Option:Put parking ban on roundabout branches", + }; + + #endregion + #region Prioirty road options public static CheckboxOption PriorityRoad_CrossMainR = new CheckboxOption("PriorityRoad_CrossMainR") { Label = "Priority roads.Option:Allow pedestrian crossings on main road", @@ -44,7 +69,7 @@ public static class OptionsMassEditTab { public static CheckboxOption PriorityRoad_AllowLeftTurns = new CheckboxOption("PriorityRoad_AllowLeftTurns") { Label = "Priority roads.Option:Allow far turns", - Tooltip = "Priority roads.Tooltipn:Allow far turns" + Tooltip = "Priority roads.Tooltipn:Allow far turns", }; public static CheckboxOption PriorityRoad_EnterBlockedYeild = @@ -55,10 +80,9 @@ public static class OptionsMassEditTab { public static CheckboxOption PriorityRoad_StopAtEntry = new CheckboxOption("PriorityRoad_StopAtEntry") { Label = "Priority roads.Option:Stop signs on entry", - Tooltip = "Priority roads.Tooltip:Stop signs on entry" + Tooltip = "Priority roads.Tooltip:Stop signs on entry", }; - - + #endregion public static void MakeSettings_MassEdit(ExtUITabstrip tabStrip, int tabIndex) { @@ -74,6 +98,10 @@ internal static void MakePanel_MassEdit(UIHelperBase panelHelper) { RoundAboutQuickFix_StayInLaneNearRabout.AddUI(raboutGroup); RoundAboutQuickFix_DedicatedExitLanes.AddUI(raboutGroup); RoundAboutQuickFix_PrioritySigns.AddUI(raboutGroup); + RoundAboutQuickFix_KeepClearYieldR.AddUI(raboutGroup); + RoundAboutQuickFix_RealisticSpeedLimits.AddUI(raboutGroup); + RoundAboutQuickFix_ParkingBanMainR.AddUI(raboutGroup); + RoundAboutQuickFix_ParkingBanYieldR.AddUI(raboutGroup); UIHelperBase priorityRoadGroup = panelHelper.AddGroup(T("MassEdit.Group.Priority roads")); PriorityRoad_CrossMainR.AddUI(priorityRoadGroup); diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index e8caeefbe..d6bd490e5 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -320,6 +320,14 @@ + + + + + + + + diff --git a/TLM/TLM/UI/Helpers/CheckboxOption.cs b/TLM/TLM/UI/Helpers/CheckboxOption.cs index 60a0bf64c..dbf12490d 100644 --- a/TLM/TLM/UI/Helpers/CheckboxOption.cs +++ b/TLM/TLM/UI/Helpers/CheckboxOption.cs @@ -23,13 +23,7 @@ public override bool Value { public override byte Save() => Value ? (byte)1 : (byte)0; public override void AddUI(UIHelperBase container) { - string T(string key) { - if (key.StartsWith("Roundabout")) { - // TODO move roundabout option keys to options.csv and remove this - return Translation.Menu.Get(key); - } - return Translation.Options.Get(key); - } + string T(string key) => Translation.Options.Get(key); _ui = container.AddCheckbox( T(Label), Value, @@ -37,6 +31,9 @@ string T(string key) { if (Tooltip!=null) { _ui.tooltip = T(Tooltip); } + if (Indent) { + State.Options.Indent(_ui); + } } diff --git a/TLM/TLM/UI/Helpers/SerializableUIOptionBase.cs b/TLM/TLM/UI/Helpers/SerializableUIOptionBase.cs index 9e7caf71f..677a676fa 100644 --- a/TLM/TLM/UI/Helpers/SerializableUIOptionBase.cs +++ b/TLM/TLM/UI/Helpers/SerializableUIOptionBase.cs @@ -31,6 +31,7 @@ public virtual TVal Value { protected TUI _ui; public string Label; public string Tooltip; + public bool Indent = false; public void DefaultOnValueChanged(TVal newVal) { Options.IsGameLoaded(); diff --git a/TLM/TLM/UI/RoadSelectionPanels.cs b/TLM/TLM/UI/RoadSelectionPanels.cs index ce78deaad..e8869dd86 100644 --- a/TLM/TLM/UI/RoadSelectionPanels.cs +++ b/TLM/TLM/UI/RoadSelectionPanels.cs @@ -1,14 +1,15 @@ namespace TrafficManager.UI { + using ColossalFramework.UI; + using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; - using ColossalFramework.UI; - using UnityEngine; - using CSUtil.Commons; - using TrafficManager.Util; - using TrafficManager.U.Button; using TrafficManager.RedirectionFramework; + using TrafficManager.U.Button; using TrafficManager.UI.SubTools.PrioritySigns; + using TrafficManager.Util; + using TrafficManager.Util.Record; + using UnityEngine; public class RoadSelectionPanels : MonoBehaviour { private const bool CREATE_NET_ADJUST_SUBPANEL = false; @@ -44,8 +45,7 @@ internal FunctionModes Function { UIPanel roadAdjustPanel_; UIPanel RoadAdjustPanel { - get - { + get { if (roadAdjustPanel_ == null) roadAdjustPanel_ = UIView.Find("AdjustRoad"); return roadAdjustPanel_; @@ -61,6 +61,8 @@ UIPanel RoadAdjustPanel { private IList panels_; private UIComponent priorityRoadToggle_; + public IRecordable Record; + #region Load public void Awake() { _function = FunctionModes.None; @@ -331,10 +333,8 @@ private void SetupAtlas() { } } - internal bool HasHoveringButton() - { - foreach (var button in buttons_ ?? Enumerable.Empty()) - { + internal bool HasHoveringButton() { + foreach (var button in buttons_ ?? Enumerable.Empty()) { if (button.IsHovered && button.isEnabled) return true; } @@ -351,43 +351,29 @@ public class ClearButtton : ButtonExt { protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Clear"); public override string SkinPrefix => Function.ToString(); // remove _RHT/_LHT postFix. internal override FunctionModes Function => FunctionModes.Clear; - protected override bool IsActive() => false; // Clear funtionality can't be undone. #568 - public override void Do() => // TODO delete all rules as part of #568 - PriorityRoad.ClearRoad(Selection); - public override void Undo() => throw new Exception("Unreachable code"); + public override IRecordable Do() => PriorityRoad.ClearRoad(Selection); } public class StopButtton : ButtonExt { protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Stop entry"); internal override FunctionModes Function => FunctionModes.Stop; - public override void Do() => + public override IRecordable Do() => PriorityRoad.FixPrioritySigns(PrioritySignsTool.PrioritySignsMassEditMode.MainStop, Selection); - public override void Undo() => - PriorityRoad.FixPrioritySigns(PrioritySignsTool.PrioritySignsMassEditMode.Delete, Selection); } public class YieldButton : ButtonExt { protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Yield entry"); internal override FunctionModes Function => FunctionModes.Yield; - public override void Do() => + public override IRecordable Do() => PriorityRoad.FixPrioritySigns(PrioritySignsTool.PrioritySignsMassEditMode.MainYield, Selection); - public override void Undo() => - PriorityRoad.FixPrioritySigns(PrioritySignsTool.PrioritySignsMassEditMode.Delete, Selection); } public class HighPriorityButtton : ButtonExt { protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:High priority"); internal override FunctionModes Function => FunctionModes.HighPriority; - public override void Do() => - PriorityRoad.FixRoad(Selection); - public override void Undo() => - PriorityRoad.ClearRoad(Selection); + public override IRecordable Do() => PriorityRoad.FixRoad(Selection); } public class RoundaboutButtton : ButtonExt { protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Roundabout"); internal override FunctionModes Function => FunctionModes.Roundabout; - public override void Do() => - RoundaboutMassEdit.Instance.FixRoundabout(Selection); - public override void Undo() => - RoundaboutMassEdit.Instance.ClearRoundabout(Selection); - + public override IRecordable Do() => RoundaboutMassEdit.Instance.FixRoundabout(Selection); public override bool ShouldDisable() { if (Length <= 1) { return true; @@ -407,7 +393,7 @@ public abstract class ButtonExt : BaseUButton { public RoadSelectionPanels Root => RoadSelectionPanels.Root; - public virtual string ButtonName => "TMPE.RoadSelectionPanel" + this.GetType().ToString(); + public virtual string ButtonName => "TMPE.RoadSelectionPanel" + this.GetType().ToString(); private static string TrafficSidePostFix => Shortcuts.RHT ? "_RHT" : "_LHT"; @@ -465,16 +451,16 @@ public void Refresh() { public override void HandleClick(UIMouseEventParameter p) => throw new Exception("Unreachable code"); - /// Handles button click on activation. Apply traffic rules here. - public abstract void Do(); + /// Handles button click on activation. Apply traffic rules here. + public abstract IRecordable Do(); - /// Handles button click on de-activation. Reset/Undo traffic rules here. - public abstract void Undo(); + /// Handles button click on de-activation. Reset/Undo traffic rules here. + public virtual void Undo() => Root.Record?.Restore(); protected override void OnClick(UIMouseEventParameter p) { if (!IsActive()) { Root.Function = this.Function; - Do(); + Root.Record = Do(); Root.EnqueueAction(Root.ShowMassEditOverlay); } else { Root.Function = FunctionModes.None; diff --git a/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs b/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs index 07a5740f3..5b022569f 100644 --- a/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs +++ b/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs @@ -11,6 +11,7 @@ namespace TrafficManager.UI.SubTools { using TrafficManager.UI.Helpers; using static TrafficManager.Util.Shortcuts; using ColossalFramework.Math; + using TrafficManager.UI.SubTools.PrioritySigns; using ColossalFramework.UI; using TrafficManager.UI.MainMenu.OSD; @@ -153,7 +154,7 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { - if (viewOnly && !Options.parkingRestrictionsOverlay) { + if (viewOnly && !Options.parkingRestrictionsOverlay && !MassEditOverlay.IsActive) { return; } @@ -252,7 +253,7 @@ private NetInfo.Direction DrawParkingRestrictionHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { - if (viewOnly && !Options.parkingRestrictionsOverlay) { + if (viewOnly && !Options.parkingRestrictionsOverlay && !MassEditOverlay.IsActive) { return NetInfo.Direction.None; } @@ -294,7 +295,7 @@ private NetInfo.Direction DrawParkingRestrictionHandles(ushort segmentId, size, size); - if (Options.speedLimitsOverlay) { + if (Options.speedLimitsOverlay || MassEditOverlay.IsActive) { boundingBox.y -= size + 10f; } diff --git a/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs b/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs index 14b78e722..c08da5371 100644 --- a/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs +++ b/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs @@ -16,6 +16,8 @@ namespace TrafficManager.UI.SubTools.PrioritySigns { using TrafficManager.UI.Textures; using TrafficManager.Util; using UnityEngine; + using TrafficManager.Util.Record; + using static Util.Shortcuts; using static TrafficManager.Util.SegmentTraverser; public class PrioritySignsTool @@ -23,15 +25,32 @@ public class PrioritySignsTool UI.MainMenu.IOnscreenDisplayProvider { public enum PrioritySignsMassEditMode { + Min=0, MainYield = 0, MainStop = 1, YieldMain = 2, StopMain = 3, - Delete = 4, + Undo = 4, + Max = 4, + } + + IRecordable record_; + + enum ModifyMode { + None, + PriorityRoad, + HighPriorityRoad, + HighPriorityJunction, + Roundabout, + } + static class PrevHoveredState { + public static ushort SegmentId; + public static ushort NodeId; + public static ModifyMode Mode; } private readonly HashSet currentPriorityNodeIds; - private PrioritySignsMassEditMode massEditMode = PrioritySignsMassEditMode.MainYield; + private PrioritySignsMassEditMode massEditMode = PrioritySignsMassEditMode.Min; public PrioritySignsTool(TrafficManagerTool mainTool) : base(mainTool) { @@ -39,9 +58,7 @@ public PrioritySignsTool(TrafficManagerTool mainTool) } public override void OnPrimaryClickOverlay() { - bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); - bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - if(ctrlDown || shiftDown) { + if(ControlIsPressed || ShiftIsPressed) { if (HoveredSegmentId == 0) { return; } @@ -49,40 +66,39 @@ public override void OnPrimaryClickOverlay() { MainTool.RequestOnscreenDisplayUpdate(); } - // TODO provide revert/clear mode issue #568 - if (ctrlDown && shiftDown) { + if(massEditMode == PrioritySignsMassEditMode.Undo) { + record_?.Restore(); + } + else if (ControlIsPressed && ShiftIsPressed) { Log.Info("Before FixRoundabout/FixRoad."); // log time for benchmarking. - bool isRoundabout = RoundaboutMassEdit.Instance.FixRoundabout(HoveredSegmentId); + bool isRoundabout = RoundaboutMassEdit.Instance.FixRoundabout( + HoveredSegmentId, out record_); if (!isRoundabout) { - PriorityRoad.FixRoad(HoveredSegmentId); + record_ = PriorityRoad.FixRoad(HoveredSegmentId); } + // TODO: benchmark why bulk setup takes a long time. Log.Info("After FixRoundabout/FixRoad. Before RefreshMassEditOverlay"); // log time for benchmarking. RefreshMassEditOverlay(); Log.Info("After RefreshMassEditOverlay."); // log time for benchmarking. - return; - } else if (ctrlDown) { + } else if (ControlIsPressed) { + record_ = new TrafficRulesRecord(); + (record_ as TrafficRulesRecord).AddNodeAndSegmentEnds(HoveredNodeId); PriorityRoad.FixHighPriorityJunction(HoveredNodeId); - RefreshMassEditOverlay(); - return; } - if (shiftDown) { + else if (ShiftIsPressed) { bool isRoundabout = RoundaboutMassEdit.Instance.TraverseLoop(HoveredSegmentId, out var segmentList); if (!isRoundabout) { - segmentList = SegmentTraverser.Traverse( + var segments = SegmentTraverser.Traverse( HoveredSegmentId, TraverseDirection.AnyDirection, TraverseSide.Straight, SegmentStopCriterion.None, (_)=>true); + segmentList = new List(segments); } - PriorityRoad.FixPrioritySigns(massEditMode,segmentList); - - // cycle mass edit mode - massEditMode = - (PrioritySignsMassEditMode)(((int)massEditMode + 1) % - Enum.GetValues(typeof(PrioritySignsMassEditMode)) - .GetLength(0)); + PriorityRoad.FixPrioritySigns(massEditMode, segmentList); + record_ = null; } else { if (TrafficPriorityManager.Instance.HasNodePrioritySign(HoveredNodeId)) { return; @@ -97,8 +113,24 @@ public override void OnPrimaryClickOverlay() { // Log._Debug($"PrioritySignsTool.OnPrimaryClickOverlay: SelectedNodeId={SelectedNodeId}"); } - // update priority node cache - RefreshCurrentPriorityNodeIds(); + // cycle mass edit mode + if (ControlIsPressed) { + massEditMode = + massEditMode != PrioritySignsMassEditMode.MainYield ? + PrioritySignsMassEditMode.MainYield : + PrioritySignsMassEditMode.Undo; + } else if (ShiftIsPressed) { + massEditMode++; + if (massEditMode > PrioritySignsMassEditMode.Max) { + massEditMode = PrioritySignsMassEditMode.Min; + } + } + + // refresh cache + if(ControlIsPressed) + RefreshMassEditOverlay(); + else + RefreshCurrentPriorityNodeIds(); } public override void OnToolGUI(Event e) { } @@ -129,23 +161,16 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { return; } - bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); - bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - - MassEditOverlay.Show = ctrlDown; + ModifyMode mode = ModifyMode.None; - if (ctrlDown) { - massEditMode = PrioritySignsMassEditMode.MainYield; - } + MassEditOverlay.Show = ControlIsPressed; if (HoveredSegmentId == 0) { - if(shiftDown) { - massEditMode = PrioritySignsMassEditMode.MainYield; - } + massEditMode = PrioritySignsMassEditMode.Min; return; } - if (shiftDown) { + if (Shortcuts.ShiftIsPressed) { bool isRoundabout = RoundaboutMassEdit.Instance.TraverseLoop(HoveredSegmentId, out var segmentList); Color color = MainTool.GetToolColor(Input.GetMouseButton(0), false); if (isRoundabout) { @@ -173,30 +198,46 @@ ref Singleton.instance.m_segments.m_buffer[ return true; }); } - return; - } else if (ctrlDown) { + if (!ControlIsPressed) + mode = ModifyMode.PriorityRoad; + else if (!isRoundabout) + mode = ModifyMode.HighPriorityRoad; + else + mode = ModifyMode.Roundabout; + + if (mode != PrevHoveredState.Mode || HoveredSegmentId != PrevHoveredState.SegmentId) { + massEditMode = PrioritySignsMassEditMode.Min; + } + } else if (ControlIsPressed) { MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); - return; - } + mode = ModifyMode.HighPriorityJunction; + if (mode != PrevHoveredState.Mode || HoveredNodeId != PrevHoveredState.NodeId) { + massEditMode = PrioritySignsMassEditMode.Min; + } + } else { + massEditMode = PrioritySignsMassEditMode.Min; - massEditMode = PrioritySignsMassEditMode.MainYield; + if (HoveredNodeId == SelectedNodeId) { + return; + } - if (HoveredNodeId == SelectedNodeId) { - return; - } + // no highlight for existing priority node in sign mode + if (TrafficPriorityManager.Instance.HasNodePrioritySign(HoveredNodeId)) { + // Log._Debug($"PrioritySignsTool.RenderOverlay: HasNodePrioritySign({HoveredNodeId})=true"); + return; + } - // no highlight for existing priority node in sign mode - if (TrafficPriorityManager.Instance.HasNodePrioritySign(HoveredNodeId)) { - // Log._Debug($"PrioritySignsTool.RenderOverlay: HasNodePrioritySign({HoveredNodeId})=true"); - return; - } + if (!TrafficPriorityManager.Instance.MayNodeHavePrioritySigns(HoveredNodeId)) { + // Log._Debug($"PrioritySignsTool.RenderOverlay: MayNodeHavePrioritySigns({HoveredNodeId})=false"); + return; + } - if (!TrafficPriorityManager.Instance.MayNodeHavePrioritySigns(HoveredNodeId)) { - // Log._Debug($"PrioritySignsTool.RenderOverlay: MayNodeHavePrioritySigns({HoveredNodeId})=false"); - return; + MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); } - MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); + PrevHoveredState.Mode = mode; + PrevHoveredState.SegmentId = HoveredSegmentId; + PrevHoveredState.NodeId = HoveredNodeId; } private void RefreshCurrentPriorityNodeIds() { diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs index b0b0cfbe6..9ff9e56f8 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs @@ -15,6 +15,7 @@ namespace TrafficManager.UI.SubTools.SpeedLimits { using TrafficManager.Util; using static TrafficManager.Util.Shortcuts; using UnityEngine; + using TrafficManager.UI.SubTools.PrioritySigns; public class SpeedLimitsTool : LegacySubTool { public const int @@ -244,7 +245,7 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { - if (viewOnly && !Options.speedLimitsOverlay) { + if (viewOnly && !Options.speedLimitsOverlay && !MassEditOverlay.IsActive) { return; } @@ -720,7 +721,7 @@ private bool DrawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { - if (viewOnly && !Options.speedLimitsOverlay) { + if (viewOnly && !Options.speedLimitsOverlay && !MassEditOverlay.IsActive) { return false; } bool ret = false; diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 3b25d7c11..69cf6b13e 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -260,6 +260,14 @@ public ToolMode GetToolMode() { public void SetToolMode(ToolMode newToolMode) { ToolMode oldToolMode = toolMode_; + if(toolMode_ != ToolMode.None) { + // Make it impossible for user to undo changes performed by Road selection panels + // after changing traffic rule vis other tools. + // TODO: This code will not be necessary when we implement intent. + SimulationManager.instance.m_ThreadingWrapper.QueueMainThread(RoadSelectionPanels.RoadWorldInfoPanel.Hide); + RoadSelectionPanels.Root.Function = RoadSelectionPanels.FunctionModes.None; + } + // ToolModeChanged does not count timed traffic light submodes as a same tool bool toolModeChanged = newToolMode != toolMode_; @@ -406,12 +414,13 @@ void DefaultRenderOverlay(RenderManager.CameraInfo cameraInfo) } bool isRoundabout = RoundaboutMassEdit.Instance.TraverseLoop(HoveredSegmentId, out var segmentList); if (!isRoundabout) { - segmentList = SegmentTraverser.Traverse( + var segments = SegmentTraverser.Traverse( HoveredSegmentId, TraverseDirection.AnyDirection, TraverseSide.Straight, SegmentStopCriterion.None, (_) => true); + segmentList = new List(segmentList); } foreach (ushort segmentId in segmentList ?? Enumerable.Empty()) { ref NetSegment seg = ref Singleton.instance.m_segments.m_buffer[segmentId]; @@ -546,12 +555,13 @@ void DefaultOnToolGUI(Event e) { if (ReadjustPathMode) { bool isRoundabout = RoundaboutMassEdit.Instance.TraverseLoop(HoveredSegmentId, out var segmentList); if (!isRoundabout) { - segmentList = SegmentTraverser.Traverse( + var segments = SegmentTraverser.Traverse( HoveredSegmentId, TraverseDirection.AnyDirection, TraverseSide.Straight, SegmentStopCriterion.None, (_) => true); + segmentList = new List(segments); } RoadSelectionUtil.SetRoad(HoveredSegmentId, segmentList); } diff --git a/TLM/TLM/Util/PriorityRoad.cs b/TLM/TLM/Util/PriorityRoad.cs index 3c6d9c73c..7c41c0cef 100644 --- a/TLM/TLM/Util/PriorityRoad.cs +++ b/TLM/TLM/Util/PriorityRoad.cs @@ -1,30 +1,32 @@ namespace TrafficManager.Util { + using CSUtil.Commons; + using GenericGameBridge.Service; using System; using System.Collections.Generic; + using TrafficManager.API.Manager; using TrafficManager.API.Traffic.Data; using TrafficManager.API.Traffic.Enums; using TrafficManager.Manager.Impl; - using GenericGameBridge.Service; - using CSUtil.Commons; + using TrafficManager.State; + using TrafficManager.UI.SubTools.PrioritySigns; + using TrafficManager.Util.Record; using UnityEngine; using static TrafficManager.Util.SegmentTraverser; - using TrafficManager.State; using static TrafficManager.Util.Shortcuts; - using TrafficManager.API.Manager; - using TrafficManager.UI.SubTools.PrioritySigns; /// /// Utility for mass edit of prioirty roads. /// public static class PriorityRoad { - public static void FixPrioritySigns( + public static IRecordable FixPrioritySigns( PrioritySignsTool.PrioritySignsMassEditMode massEditMode, - List segmentList) - { + List segmentList) { if (segmentList == null || segmentList.Count == 0) { - return; + return null; } + IRecordable record = RecordRoad(segmentList); + var primaryPrioType = PriorityType.None; var secondaryPrioType = PriorityType.None; @@ -81,18 +83,13 @@ void ApplyPrioritySigns(ushort segmentId, bool startNode) { } } - // TODO avoid settin up the same node two times.s - foreach(ushort segId in segmentList) { + // TODO avoid settin up the same node two times. + foreach (ushort segId in segmentList) { ApplyPrioritySigns(segId, true); ApplyPrioritySigns(segId, false); } - } - - private static void Swap(this List list, int i1, int i2) { - ushort temp = list[i1]; - list[i1] = list[i2]; - list[i2] = temp; + return record; } private static LaneArrows ToLaneArrows(ArrowDirection dir) { @@ -112,22 +109,23 @@ private static LaneArrows ToLaneArrows(ArrowDirection dir) { /// Quick-setups as priority junction: for every junctions on the road contianing /// the input segment traversing straight. /// - public static void FixRoad(ushort initialSegmentId) { + public static IRecordable FixRoad(ushort initialSegmentId) { + // Create segment list such that the first and last segments are at path end. + List segmentList = new List(40); SegmentTraverser.Traverse( - initialSegmentId, - TraverseDirection.AnyDirection, - TraverseSide.Straight, - SegmentStopCriterion.None, - VisitorFunc); - } - - private static bool VisitorFunc(SegmentVisitData data) { - ushort segmentId = data.CurSeg.segmentId; - foreach (bool startNode in Constants.ALL_BOOL) { - ushort nodeId = netService.GetSegmentNodeId(segmentId, startNode); - FixHighPriorityJunction(nodeId); - } - return true; + initialSegmentId, + TraverseDirection.AnyDirection, + TraverseSide.Straight, + SegmentStopCriterion.None, + data => { + if (data.ViaInitialStartNode) + segmentList.Add(data.CurSeg.segmentId); + else + segmentList.Insert(0, data.CurSeg.segmentId); + return true; + }); + + return FixRoad(segmentList); } /// the node of that is not shared @@ -144,11 +142,15 @@ private static ushort GetSharedOrOtherNode(ushort segmentId, ushort otherSegment /// Quick-setups as priority junction: for every junctions on the road contianing /// the input segment traversing straight. /// - public static void FixRoad(List segmentList) { + public static IRecordable FixRoad(List segmentList) { + if (segmentList == null || segmentList.Count == 0) + return null; + IRecordable record = RecordRoad(segmentList); + ushort firstNodeId = GetSharedOrOtherNode(segmentList[0], segmentList[1], out _); int last = segmentList.Count - 1; - ushort lastNodeId = GetSharedOrOtherNode(segmentList[last], segmentList[last-1], out _); - if(firstNodeId == lastNodeId) { + ushort lastNodeId = GetSharedOrOtherNode(segmentList[last], segmentList[last - 1], out _); + if (firstNodeId == lastNodeId) { firstNodeId = lastNodeId = 0; } @@ -163,6 +165,7 @@ public static void FixRoad(List segmentList) { } } } + return record; } private static bool IsStraighOneWay(ushort segmentId0, ushort segmentId1) { @@ -211,15 +214,15 @@ internal static bool ArrangeT(List segmentList) { // expected a one way road and 2 two-way roads. return false; } else if (!oneway0) { - segmentList.Swap( 0, 2); + segmentList.Swap(0, 2); } else if (!oneway1) { - segmentList.Swap( 1, 2); + segmentList.Swap(1, 2); } // slot 0: incomming road. // slot 1: outgoing road. if (netService.GetHeadNode(segmentList[1]) == netService.GetTailNode(segmentList[0])) { - segmentList.Swap( 0, 1); + segmentList.Swap(0, 1); return true; } @@ -245,7 +248,7 @@ ref GetSeg(segmentIdSrc), SetArrows(segmentList[0], segmentList[2]); SetArrows(segmentList[2], segmentList[1]); - foreach(ushort segmentId in segmentList) { + foreach (ushort segmentId in segmentList) { FixMajorSegmentRules(segmentId, nodeId); } } @@ -371,7 +374,7 @@ private static void FixHighPriorityJunctionHelper(ushort nodeId, List no } else { FixMajorSegmentRules(segmentId, nodeId); } - if(!ignoreLanes) { + if (!ignoreLanes) { FixMajorSegmentLanes(segmentId, nodeId); } } else { @@ -397,7 +400,7 @@ private static void FixMajorSegmentRules(ushort segmentId, ushort nodeId) { Log._Debug($"FixMajorSegmentRules({segmentId}, {nodeId}) was called"); bool startNode = (bool)netService.IsStartNode(segmentId, nodeId); JunctionRestrictionsManager.Instance.SetEnteringBlockedJunctionAllowed(segmentId, startNode, true); - if(!OptionsMassEditTab.PriorityRoad_CrossMainR) { + if (!OptionsMassEditTab.PriorityRoad_CrossMainR) { JunctionRestrictionsManager.Instance.SetPedestrianCrossingAllowed(segmentId, startNode, false); } TrafficPriorityManager.Instance.SetPrioritySign(segmentId, startNode, PriorityType.Main); @@ -453,7 +456,7 @@ bool IsMain(ushort segId) { } Log._Debug($"HasAccelerationLane: segmentId:{segmentId} MainToward={MainToward} MainAgainst={MainAgainst} "); - if (IsMain(MainToward) && IsMain(MainAgainst) ) { + if (IsMain(MainToward) && IsMain(MainAgainst)) { int Yt = CountLanesTowardJunction(segmentId, nodeId); // Yeild Toward. int Mt = CountLanesTowardJunction(MainToward, nodeId); // Main Toward. int Ma = CountLanesAgainstJunction(MainAgainst, nodeId); // Main Against. @@ -547,9 +550,9 @@ private static void FixMinorSegmentLanes(ushort segmentId, ushort nodeId, List 0 && bnear && turnArrow == LaneArrows.Forward) { + if (srcLaneCount > 0 && bnear && turnArrow == LaneArrows.Forward) { LaneArrowManager.Instance.AddLaneArrows( laneList[sideLaneIndex].laneId, nearArrow); @@ -604,34 +607,44 @@ private static int CountRoadVehicleLanes(ushort segmentId) { /// /// public static void ClearNode(ushort nodeId) { - TrafficPriorityManager TPMan = Constants.ManagerFactory.TrafficPriorityManager as TrafficPriorityManager; - IJunctionRestrictionsManager JPMan = Constants.ManagerFactory.JunctionRestrictionsManager; LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(nodeId); netService.IterateNodeSegments(nodeId, (ushort segmentId, ref NetSegment seg) => { ref NetNode node = ref GetNode(nodeId); bool startNode = (bool)netService.IsStartNode(segmentId, nodeId); - TPMan.SetPrioritySign(segmentId, startNode, PriorityType.None); - JPMan.SetPedestrianCrossingAllowed(segmentId, startNode, TernaryBool.Undefined); - JPMan.SetEnteringBlockedJunctionAllowed(segmentId, startNode, TernaryBool.Undefined); - if (ExtNodeManager.JunctionHasOnlyHighwayRoads(nodeId)) { - JPMan.SetLaneChangingAllowedWhenGoingStraight(segmentId, startNode, TernaryBool.Undefined); - } + TrafficPriorityManager.Instance.SetPrioritySign(segmentId, startNode, PriorityType.None); + JunctionRestrictionsManager.Instance.ClearSegmentEnd(segmentId, startNode); LaneArrowManager.Instance.ResetLaneArrows(segmentId, startNode); return true; }); } /// - /// Clears all rules put by PriorityRoad.FixJunction() + /// Clears all rules traffic rules accross given segmeent list. + /// Clears segment ends of connected branchs as well. /// - /// - public static void ClearRoad(List segmentList) { + public static IRecordable ClearRoad(List segmentList) { + if (segmentList == null || segmentList.Count == 0) + return null; + IRecordable record = RecordRoad(segmentList); foreach (ushort segmentId in segmentList) { - foreach (bool startNode in Constants.ALL_BOOL) { - ushort nodeId = netService.GetSegmentNodeId(segmentId, startNode); - ClearNode(nodeId); - } + ParkingRestrictionsManager.Instance.SetParkingAllowed(segmentId, true); + SpeedLimitManager.Instance.SetSpeedLimit(segmentId, null); + VehicleRestrictionsManager.Instance.ClearVehicleRestrictions(segmentId); + ClearNode(netService.GetSegmentNodeId(segmentId, true)); + ClearNode(netService.GetSegmentNodeId(segmentId, false)); } + return record; + } + + /// + /// records traffic rules state of everything affected by FixRoad() or FixPrioritySigns() + /// + public static IRecordable RecordRoad(List segmentList) { + TrafficRulesRecord record = new TrafficRulesRecord(); + foreach (ushort segmetnId in segmentList) + record.AddCompleteSegment(segmetnId); + record.Record(); + return record; } } //end class } diff --git a/TLM/TLM/Util/Record/IRecordable.cs b/TLM/TLM/Util/Record/IRecordable.cs new file mode 100644 index 000000000..05dbc1aca --- /dev/null +++ b/TLM/TLM/Util/Record/IRecordable.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace TrafficManager.Util.Record { + public interface IRecordable { + void Record(); + void Restore(); + } +} diff --git a/TLM/TLM/Util/Record/LaneArrowsRecord.cs b/TLM/TLM/Util/Record/LaneArrowsRecord.cs new file mode 100644 index 000000000..a119fcd4a --- /dev/null +++ b/TLM/TLM/Util/Record/LaneArrowsRecord.cs @@ -0,0 +1,41 @@ +namespace TrafficManager.Util.Record { + using ColossalFramework; + using CSUtil.Commons; + using System.Collections.Generic; + using TrafficManager.API.Traffic.Enums; + using TrafficManager.Manager.Impl; + using static TrafficManager.Util.Shortcuts; + + public class LaneArrowsRecord : IRecordable { + public uint LaneId; + + private LaneArrows arrows_; + + public void Record() { + arrows_ = LaneArrowManager.Instance.GetFinalLaneArrows(LaneId); + } + + public void Restore() { + //Log._Debug($"Restore: SetLaneArrows({LaneId}, {arrows_})"); + LaneArrowManager.Instance.SetLaneArrows(LaneId, arrows_); + } + + public static List GetLanes(ushort segmentId, bool startNode) { + var ret = new List(); + var lanes = netService.GetSortedLanes( + segmentId, + ref segmentId.ToSegment(), + startNode, + LaneArrowManager.LANE_TYPES, + LaneArrowManager.VEHICLE_TYPES, + sort: false); + foreach(var lane in lanes) { + LaneArrowsRecord laneData = new LaneArrowsRecord { + LaneId = lane.laneId, + }; + ret.Add(laneData); + } + return ret; + } + } +} diff --git a/TLM/TLM/Util/Record/LaneConnectionRecord.cs b/TLM/TLM/Util/Record/LaneConnectionRecord.cs new file mode 100644 index 000000000..28af56aa3 --- /dev/null +++ b/TLM/TLM/Util/Record/LaneConnectionRecord.cs @@ -0,0 +1,82 @@ +namespace TrafficManager.Util.Record { + using ColossalFramework; + using CSUtil.Commons; + using GenericGameBridge.Service; + using System.Collections.Generic; + using System.Linq; + using TrafficManager.Manager.Impl; + using static TrafficManager.Util.Shortcuts; + + public class LaneConnectionRecord : IRecordable { + public uint LaneId; + public byte LaneIndex; + public bool StartNode; + + private uint[] connections_; + + private static LaneConnectionManager connMan => LaneConnectionManager.Instance; + + private uint[] GetCurrentConnections() => connMan.GetLaneConnections(LaneId, StartNode); + + public void Record() { + connections_ = GetCurrentConnections(); + //Log._Debug($"LaneConnectionRecord.Record: connections_=" + connections_.ToSTR()); + + if (connections_ != null) + connections_ = (uint[])connections_.Clone(); + } + + public void Restore() { + if (connections_ == null) { + connMan.RemoveLaneConnections(LaneId, StartNode); + return; + } + var currentConnections = GetCurrentConnections(); + //Log._Debug($"currentConnections=" + currentConnections.ToSTR()); + //Log._Debug($"connections_=" + connections_.ToSTR()); + + foreach (uint targetLaneId in connections_) { + if (currentConnections == null || !currentConnections.Contains(targetLaneId)) { + connMan.AddLaneConnection(LaneId, targetLaneId, StartNode); + } + } + foreach (uint targetLaneId in currentConnections ?? Enumerable.Empty()) { + if (!connections_.Contains(targetLaneId)) { + connMan.RemoveLaneConnection(LaneId, targetLaneId, StartNode); + } + } + } + + public static List GetLanes(ushort nodeId) { + var ret = new List(); + ref NetNode node = ref nodeId.ToNode(); + for (int i = 0; i < 8; ++i) { + ushort segmentId = node.GetSegment(i); + if (segmentId == 0) continue; + bool Handler( + uint laneId, + ref NetLane lane, + NetInfo.Lane laneInfo, + ushort segmentId, + ref NetSegment segment, + byte laneIndex) { + bool match = (laneInfo.m_laneType & LaneConnectionManager.LANE_TYPES) != 0 && + (laneInfo.m_vehicleType & LaneConnectionManager.VEHICLE_TYPES) != 0; + if (!match) + return true; + var laneData = new LaneConnectionRecord { + LaneId = laneId, + LaneIndex = laneIndex, + StartNode = (bool)netService.IsStartNode(segmentId, nodeId), + }; + ret.Add(laneData); + return true; + } + netService.IterateSegmentLanes( + segmentId, + Handler); + } + return ret; + } + } +} diff --git a/TLM/TLM/Util/Record/NodeRecord.cs b/TLM/TLM/Util/Record/NodeRecord.cs new file mode 100644 index 000000000..4acc5617c --- /dev/null +++ b/TLM/TLM/Util/Record/NodeRecord.cs @@ -0,0 +1,45 @@ +namespace TrafficManager.Util.Record { + using System.Collections.Generic; + using TrafficManager.Manager.Impl; + using static TrafficManager.Util.Shortcuts; + + class NodeRecord : IRecordable { + public ushort NodeId { get; private set; } + public NodeRecord(ushort nodeId) => NodeId = nodeId; + + private bool trafficLight_; + private List lanes_; + private static TrafficLightManager tlMan => TrafficLightManager.Instance; + + public void Record() { + trafficLight_ = tlMan.HasTrafficLight(NodeId, ref NodeId.ToNode()); + lanes_ = LaneConnectionRecord.GetLanes(NodeId); + foreach (LaneConnectionRecord sourceLane in lanes_) { + sourceLane.Record(); + } + } + + public void Restore() { + SetTrafficLight(NodeId, trafficLight_); + foreach (LaneConnectionRecord sourceLane in lanes_) { + sourceLane.Restore(); + } + } + + private static bool SetTrafficLight(ushort nodeId, bool flag) { + // TODO move code to manager. + bool currentValue = tlMan.HasTrafficLight(nodeId, ref nodeId.ToNode()); + if (currentValue == flag) + return true; + bool canChangeValue = tlMan.CanToggleTrafficLight( + nodeId, + flag, + ref nodeId.ToNode(), + out _); + if (!canChangeValue) { + return false; + } + return tlMan.SetTrafficLight(nodeId, flag, ref nodeId.ToNode()); + } + } +} diff --git a/TLM/TLM/Util/Record/SegmentEndRecord.cs b/TLM/TLM/Util/Record/SegmentEndRecord.cs new file mode 100644 index 000000000..6d9ed6f2f --- /dev/null +++ b/TLM/TLM/Util/Record/SegmentEndRecord.cs @@ -0,0 +1,66 @@ +namespace TrafficManager.Util.Record { + using CSUtil.Commons; + using System.Collections.Generic; + using TrafficManager.API.Traffic; + using TrafficManager.API.Traffic.Enums; + using TrafficManager.Manager.Impl; + + class SegmentEndRecord : IRecordable { + public ushort SegmentId { get; private set; } + public bool StartNode { get; private set; } + + public SegmentEndRecord(int segmentEndIndex) { + SegmentEndManager.Instance. + GetSegmentAndNodeFromIndex(segmentEndIndex, out ushort segmentId, out bool startNode); + SegmentId = segmentId; + StartNode = startNode; + } + + private TernaryBool uturnAllowed_; + private TernaryBool nearTurnOnRedAllowed_; + private TernaryBool farTurnOnRedAllowed_; + private TernaryBool laneChangingAllowedWhenGoingStraight_; + private TernaryBool enteringBlockedJunctionAllowed_; + private TernaryBool pedestrianCrossingAllowed_; + + private PriorityType prioirtySign_; + private List lanes_; + + private static TrafficPriorityManager priorityMan => TrafficPriorityManager.Instance; + private static JunctionRestrictionsManager JRMan => JunctionRestrictionsManager.Instance; + + public void Record() { + uturnAllowed_ = JRMan.GetUturnAllowed(SegmentId, StartNode); + nearTurnOnRedAllowed_ = JRMan.GetNearTurnOnRedAllowed(SegmentId, StartNode); + farTurnOnRedAllowed_ = JRMan.GetFarTurnOnRedAllowed(SegmentId, StartNode); + laneChangingAllowedWhenGoingStraight_ = JRMan.GetLaneChangingAllowedWhenGoingStraight(SegmentId, StartNode); + enteringBlockedJunctionAllowed_ = JRMan.GetEnteringBlockedJunctionAllowed(SegmentId, StartNode); + pedestrianCrossingAllowed_ = JRMan.GetPedestrianCrossingAllowed(SegmentId, StartNode); + + prioirtySign_ = priorityMan.GetPrioritySign(SegmentId, StartNode); + + lanes_ = LaneArrowsRecord.GetLanes(SegmentId, StartNode); + foreach(IRecordable lane in lanes_) + lane.Record(); + } + + public void Restore() { + foreach (IRecordable lane in lanes_) + lane.Restore(); + + if (priorityMan.MaySegmentHavePrioritySign(SegmentId, StartNode) && + prioirtySign_ != priorityMan.GetPrioritySign(SegmentId, StartNode)) { + //TODO fix manager code. + priorityMan.SetPrioritySign(SegmentId, StartNode, prioirtySign_); + } + + // all necessary checks are performed internally. + JRMan.SetUturnAllowed(SegmentId, StartNode, uturnAllowed_); + JRMan.SetNearTurnOnRedAllowed(SegmentId, StartNode, nearTurnOnRedAllowed_); + JRMan.SetFarTurnOnRedAllowed(SegmentId, StartNode, farTurnOnRedAllowed_); + JRMan.SetLaneChangingAllowedWhenGoingStraight(SegmentId, StartNode, laneChangingAllowedWhenGoingStraight_); + JRMan.SetEnteringBlockedJunctionAllowed(SegmentId, StartNode, enteringBlockedJunctionAllowed_); + JRMan.SetPedestrianCrossingAllowed(SegmentId, StartNode, pedestrianCrossingAllowed_); + } + } +} diff --git a/TLM/TLM/Util/Record/SegmentRecord.cs b/TLM/TLM/Util/Record/SegmentRecord.cs new file mode 100644 index 000000000..1bb297435 --- /dev/null +++ b/TLM/TLM/Util/Record/SegmentRecord.cs @@ -0,0 +1,34 @@ +namespace TrafficManager.Util.Record { + using CSUtil.Commons; + using System.Collections.Generic; + using TrafficManager.Manager.Impl; + + // TODO record vehicle restrictions. + public class SegmentRecord : IRecordable { + public ushort SegmentId { get; private set;} + public SegmentRecord(ushort segmentId) => SegmentId = segmentId; + + private bool parkingForward_; + private bool parkingBackward_; + + private List lanes_; + + private static ParkingRestrictionsManager pMan => ParkingRestrictionsManager.Instance; + + public void Record() { + parkingForward_ = pMan.IsParkingAllowed(SegmentId, NetInfo.Direction.Forward); + parkingBackward_ = pMan.IsParkingAllowed(SegmentId, NetInfo.Direction.Backward); + lanes_ = SpeedLimitLaneRecord.GetLanes(SegmentId); + foreach (var lane in lanes_) + lane.Record(); + } + + public void Restore() { + // TODO fix SetParkingAllowed + pMan.SetParkingAllowed(SegmentId, NetInfo.Direction.Forward, parkingForward_); + pMan.SetParkingAllowed(SegmentId, NetInfo.Direction.Backward, parkingBackward_); + foreach (var lane in lanes_) + lane.Restore(); + } + } +} diff --git a/TLM/TLM/Util/Record/SpeedLimitLaneRecord.cs b/TLM/TLM/Util/Record/SpeedLimitLaneRecord.cs new file mode 100644 index 000000000..931c9008a --- /dev/null +++ b/TLM/TLM/Util/Record/SpeedLimitLaneRecord.cs @@ -0,0 +1,58 @@ +namespace TrafficManager.Util.Record { + using ColossalFramework; + using CSUtil.Commons; + using System.Collections.Generic; + using TrafficManager.API.Traffic.Enums; + using TrafficManager.Manager.Impl; + using static TrafficManager.Util.Shortcuts; + + public class SpeedLimitLaneRecord : IRecordable { + public const NetInfo.LaneType LANE_TYPES = + LaneArrowManager.LANE_TYPES | SpeedLimitManager.LANE_TYPES; + public const VehicleInfo.VehicleType VEHICLE_TYPES = + LaneArrowManager.VEHICLE_TYPES | SpeedLimitManager.VEHICLE_TYPES; + + public byte LaneIndex; + public uint LaneId; + NetInfo.Lane LaneInfo; + ushort SegmentId; + + private float? speedLimit_; // game units + + public void Record() { + speedLimit_ = SpeedLimitManager.Instance.GetCustomSpeedLimit(LaneId); + if (speedLimit_ == 0) + speedLimit_ = null; + } + + public void Restore() { + SpeedLimitManager.Instance.SetSpeedLimit( + SegmentId, + LaneIndex, + LaneInfo, + LaneId, + speedLimit_); + } + + public static List GetLanes(ushort segmentId) { + var ret = new List(); + var lanes = netService.GetSortedLanes( + segmentId, + ref segmentId.ToSegment(), + null, + LANE_TYPES, + VEHICLE_TYPES, + sort: false); + foreach(var lane in lanes) { + SpeedLimitLaneRecord laneData = new SpeedLimitLaneRecord { + LaneId = lane.laneId, + LaneIndex = lane.laneIndex, + SegmentId = segmentId, + LaneInfo = segmentId.ToSegment().Info.m_lanes[lane.laneIndex], + }; + ret.Add(laneData); + } + return ret; + } + } +} diff --git a/TLM/TLM/Util/Record/TrafficRulesRecord.cs b/TLM/TLM/Util/Record/TrafficRulesRecord.cs new file mode 100644 index 000000000..c9cd01500 --- /dev/null +++ b/TLM/TLM/Util/Record/TrafficRulesRecord.cs @@ -0,0 +1,67 @@ +namespace TrafficManager.Util.Record { + using System.Collections; + using System.Collections.Generic; + using TrafficManager.Manager.Impl; + using static Shortcuts; + + public class TrafficRulesRecord : IRecordable { + public HashSet NodeIDs = new HashSet(); + public HashSet SegmentIDs = new HashSet(); + public HashSet SegmentEndIndeces = new HashSet(); + + public List Records = new List(); + + /// + /// Records segment and both node ends. but not the segment ends. + /// + public void AddSegmentAndNodes(ushort segmentId) { + ushort node0 = segmentId.ToSegment().m_startNode; + ushort node1 = segmentId.ToSegment().m_endNode; + SegmentIDs.Add(segmentId); + NodeIDs.Add(node0); + NodeIDs.Add(node1); + } + + /// + /// Adds the input segment, both node ends, and all segment ends attached to the nodes. + /// + public void AddCompleteSegment(ushort segmentId) { + ushort node0 = segmentId.ToSegment().m_startNode; + ushort node1 = segmentId.ToSegment().m_endNode; + SegmentIDs.Add(segmentId); + AddNodeAndSegmentEnds(node0); + AddNodeAndSegmentEnds(node1); + } + + /// + /// Adds the input node and all attached segmentEnds. + /// + public void AddNodeAndSegmentEnds(ushort nodeId) { + NodeIDs.Add(nodeId); + ref NetNode node = ref nodeId.ToNode(); + for(int i = 0; i < 8; ++i) { + ushort segmentId = node.GetSegment(i); + if (segmentId == 0) continue; + bool startNode = (bool)netService.IsStartNode(segmentId, nodeId); + int index = SegmentEndManager.Instance.GetIndex(segmentId, startNode); + SegmentEndIndeces.Add(index); + } + } + + public void Record() { + foreach (ushort nodeId in NodeIDs) + Records.Add(new NodeRecord(nodeId)); + foreach(ushort segmentId in SegmentIDs) + Records.Add(new SegmentRecord(segmentId)); + foreach (int segmentEndIndex in SegmentEndIndeces) + Records.Add(new SegmentEndRecord(segmentEndIndex)); + foreach (IRecordable record in Records) + record.Record(); + } + + public void Restore() { + foreach (IRecordable record in Records) + record.Restore(); + } + } +} diff --git a/TLM/TLM/Util/RoundaboutMassEdit.cs b/TLM/TLM/Util/RoundaboutMassEdit.cs index 5c1d11b39..aa236f4a9 100644 --- a/TLM/TLM/Util/RoundaboutMassEdit.cs +++ b/TLM/TLM/Util/RoundaboutMassEdit.cs @@ -1,15 +1,18 @@ namespace TrafficManager.Util { + using ColossalFramework.Math; using CSUtil.Commons; using GenericGameBridge.Service; - using static UI.SubTools.LaneConnectorTool; - using static TrafficManager.Util.Shortcuts; - using System.Collections.Generic; + using Record; using System; + using System.Collections.Generic; + using System.Linq; using TrafficManager.API.Traffic.Data; using TrafficManager.API.Traffic.Enums; using TrafficManager.Manager.Impl; using TrafficManager.State; using UnityEngine; + using static TrafficManager.Util.Shortcuts; + using static UI.SubTools.LaneConnectorTool; public class RoundaboutMassEdit { public static RoundaboutMassEdit Instance = new RoundaboutMassEdit(); @@ -19,7 +22,18 @@ public RoundaboutMassEdit() { private List segmentList_; - private static void FixLanesRoundabout(ushort segmentId, ushort nextSegmentId) { + private static void FixSegmentRoundabout(ushort segmentId, ushort nextSegmentId) { + if (OptionsMassEditTab.RoundAboutQuickFix_ParkingBanMainR) { + ParkingRestrictionsManager.Instance.SetParkingAllowed(segmentId, false); + } + if (OptionsMassEditTab.RoundAboutQuickFix_RealisticSpeedLimits) { + float? targetSpeed = CalculatePreferedSpeed(segmentId)?.GameUnits; + float defaultSpeed = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(segmentId.ToSegment().Info); + if (targetSpeed != null && targetSpeed < defaultSpeed) { + SpeedLimitManager.Instance.SetSpeedLimit(segmentId, NetInfo.Direction.Forward, targetSpeed); + SpeedLimitManager.Instance.SetSpeedLimit(segmentId, NetInfo.Direction.Backward, targetSpeed); + } + } ushort nodeId = netService.GetHeadNode(segmentId); if (OptionsMassEditTab.RoundAboutQuickFix_StayInLaneMainR && !HasJunctionFlag(nodeId)) { @@ -140,14 +154,24 @@ internal static void FixRulesMinor(ushort segmentId, ushort nodeId) { //ignore highway rules: //TODO remove as part of issue #569 JunctionRestrictionsManager.Instance.SetLaneChangingAllowedWhenGoingStraight(segmentId, startNode, true); } // endif + + if (OptionsMassEditTab.RoundAboutQuickFix_KeepClearYieldR) { + JunctionRestrictionsManager.Instance.SetEnteringBlockedJunctionAllowed( + segmentId, + startNode, + false); + } + } - private static void FixLanesMinor(ushort segmentId, ushort nodeId) { + private static void FixSegmentMinor(ushort segmentId, ushort nodeId) { + if (OptionsMassEditTab.RoundAboutQuickFix_ParkingBanYieldR) { + ParkingRestrictionsManager.Instance.SetParkingAllowed(segmentId, false); + } int shortUnit = 4; int meterPerUnit = 8; ref NetSegment seg = ref GetSeg(segmentId); ushort otherNodeId = seg.GetOtherNode(nodeId); - if (OptionsMassEditTab.RoundAboutQuickFix_StayInLaneNearRabout && !HasJunctionFlag(otherNodeId) && seg.m_averageLength < shortUnit * meterPerUnit) { @@ -165,7 +189,7 @@ private void FixMinor(ushort nodeId) { } // end if FixRulesMinor(segmentId, nodeId); - FixLanesMinor(segmentId, nodeId); + FixSegmentMinor(segmentId, nodeId); }//end for } @@ -174,31 +198,34 @@ private void FixMinor(ushort nodeId) { /// /// /// - public bool FixRoundabout(ushort initialSegmentId) { + public bool FixRoundabout(ushort initialSegmentId, out IRecordable record) { bool isRoundabout = TraverseLoop(initialSegmentId, out var segList); if (!isRoundabout) { Log._Debug($"segment {initialSegmentId} not a roundabout."); + record = null; return false; } int count = segList.Count; Log._Debug($"\n segmentId={initialSegmentId} seglist.count={count}\n"); - FixRoundabout(segList); + record = FixRoundabout(segList); return true; } - public void FixRoundabout(List segList) { + public IRecordable FixRoundabout(List segList) { if (segList == null) - return; + return null; + IRecordable record = RecordRoundabout(segList); this.segmentList_ = segList; int count = segList.Count; for (int i = 0; i < count; ++i) { ushort segId = segList[i]; ushort nextSegId = segList[(i + 1) % count]; - FixLanesRoundabout(segId, nextSegId); + FixSegmentRoundabout(segId, nextSegId); FixRulesRoundabout(segId); FixMinor(netService.GetHeadNode(segId)); } + return record; } /// @@ -219,7 +246,7 @@ public bool TraverseLoop(ushort segmentId, out List segList) { this.segmentList_ = new List(); } bool ret; - if (segmentId == 0 || ! segMan.CalculateIsOneWay(segmentId)) { + if (segmentId == 0 || !segMan.CalculateIsOneWay(segmentId)) { ret = false; } else { ret = TraverseAroundRecursive(segmentId); @@ -246,7 +273,7 @@ public static bool IsRoundabout(List segList, bool semi = false) { } return true; } - catch (Exception e){ + catch (Exception e) { Log.Error(e.ToString()); return false; } @@ -257,7 +284,7 @@ private bool TraverseAroundRecursive(ushort segmentId) { return false; // too long. prune } segmentList_.Add(segmentId); - var segments = GetSortedSegments( segmentId); + var segments = GetSortedSegments(segmentId); foreach (var nextSegmentId in segments) { bool isRoundabout; @@ -286,9 +313,9 @@ private bool TraverseAroundRecursive(ushort segmentId) { private static List GetSortedSegments(ushort segmentId) { ushort headNodeId = netService.GetHeadNode(segmentId); bool lht = LaneArrowManager.Instance.Services.SimulationService.TrafficDrivesOnLeft; - var list0 = GetSortedSegmentsHelper( headNodeId, segmentId, ArrowDirection.Forward, !lht); - var list1 = GetSortedSegmentsHelper( headNodeId, segmentId, ArrowDirection.Left , lht); - var list2 = GetSortedSegmentsHelper( headNodeId, segmentId, ArrowDirection.Right , !lht); + var list0 = GetSortedSegmentsHelper(headNodeId, segmentId, ArrowDirection.Forward, !lht); + var list1 = GetSortedSegmentsHelper(headNodeId, segmentId, ArrowDirection.Left, lht); + var list2 = GetSortedSegmentsHelper(headNodeId, segmentId, ArrowDirection.Right, !lht); if (lht) { list0.AddRange(list1); @@ -337,7 +364,7 @@ private static List GetSortedSegmentsHelper( /// /// head node for prevSegmentId /// - private static bool IsPartofRoundabout( ushort nextSegmentId, ushort prevSegmentId, ushort headNodeId) { + private static bool IsPartofRoundabout(ushort nextSegmentId, ushort prevSegmentId, ushort headNodeId) { bool ret = nextSegmentId != 0 && nextSegmentId != prevSegmentId; ret &= segMan.CalculateIsOneWay(nextSegmentId); ret &= headNodeId == netService.GetTailNode(nextSegmentId); @@ -359,30 +386,67 @@ private bool Contains(ushort segmentId) { } /// - /// Clears all rules put by RoundAboutMassEdit.FixRoundabout() + /// Records the state of traffic rules for all stuff affected by FixRoundabout() /// - public void ClearNode(ushort nodeId) { - PriorityRoad.ClearNode(nodeId); - netService.IterateNodeSegments(nodeId, (ushort segmentId, ref NetSegment seg) => { - if (!HasJunctionFlag(nodeId)) { - // clear stay in lane. - LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(nodeId); + /// + /// + public static IRecordable RecordRoundabout(List segList) { + TrafficRulesRecord record = new TrafficRulesRecord(); + foreach (ushort segmentId in segList) + record.AddCompleteSegment(segmentId); + + // Add each minor road. + foreach (ushort nodeId in record.NodeIDs.ToArray()) { + ref NetNode node = ref nodeId.ToNode(); + if (node.CountSegments() < 3) continue; + for (int i = 0; i < 8; ++i) { + ushort segmentId = node.GetSegment(i); + if (segmentId == 0) + continue; + if (record.SegmentIDs.Contains(segmentId)) + continue; + + record.AddSegmentAndNodes(segmentId); } - return true; - }); + } + + record.Record(); + return record; } /// - /// Clears all rules put by RoundAboutMassEdit.FixRoundabout() + /// Calculates Raduis of a curved segment assuming it is part of a circle. /// - /// - public void ClearRoundabout(List segList) { - foreach (ushort segmentId in segList) { - foreach (bool startNode in Constants.ALL_BOOL) { - ushort nodeId = netService.GetSegmentNodeId(segmentId, startNode); - ClearNode(nodeId); - } + internal static float CalculateRadius(ref NetSegment segment) { + // TDOO: to calculate maximum curviture for eleptical roundabout, cut the bezier in 10 portions + // and then find the bezier with minimum raduis. + Vector2 startDir = VectorUtils.XZ(segment.m_startDirection); + Vector2 endDir = VectorUtils.XZ(segment.m_endDirection); + Vector2 startPos = VectorUtils.XZ(segment.m_startNode.ToNode().m_position); + Vector2 endPos = VectorUtils.XZ(segment.m_endNode.ToNode().m_position); + float dot = Vector2.Dot(startDir, -endDir); + float len = (startPos - endPos).magnitude; + float r = len / Mathf.Sqrt(2 - 2 * dot); // see https://github.com/CitiesSkylinesMods/TMPE/issues/793#issuecomment-616351792 + return r; + } + + + /// + /// calculates realisitic speed limit of input curved segment assuming it is part of a circle. + /// minimum speed is 10kmph. + /// + /// Null if segment is straight, otherwise if successful it retunrs calcualted speed. + private static SpeedValue? CalculatePreferedSpeed(ushort segmentId) { + float r = CalculateRadius(ref segmentId.ToSegment()); + float kmph = 11.3f * Mathf.Sqrt(r); // see https://github.com/CitiesSkylinesMods/TMPE/issues/793#issue-589462235 + Log._Debug($"CalculatePreferedSpeed radius:{r} -> kmph:{kmph}"); + if (float.IsNaN(kmph) || float.IsInfinity(kmph) || kmph < 1f) { + return null; + } + if (kmph < 10f) { + kmph = 10f; } + return SpeedValue.FromKmph((ushort)kmph); } } // end class }//end namespace \ No newline at end of file diff --git a/TLM/TLM/Util/SegmentTraverser.cs b/TLM/TLM/Util/SegmentTraverser.cs index 71dcece00..a6c2d58c1 100644 --- a/TLM/TLM/Util/SegmentTraverser.cs +++ b/TLM/TLM/Util/SegmentTraverser.cs @@ -111,7 +111,7 @@ public bool IsReversed(ushort initialSegmentId) { /// segment will be traversed (event the initial segment will be omitted). /// Specifies the stateful visitor that should be notified as soon as /// a traversable segment (which has not been traversed before) is found. - public static List Traverse(ushort initialSegmentId, + public static IEnumerable Traverse(ushort initialSegmentId, TraverseDirection direction, TraverseSide side, SegmentStopCriterion stopCrit, @@ -157,8 +157,9 @@ ref extSegEndMan.ExtSegmentEnds[ return true; }); - ushort endNodeId = Constants.ServiceFactory.NetService.GetSegmentNodeId(initialSegmentId, - false); + ushort endNodeId = Constants.ServiceFactory.NetService. + GetSegmentNodeId(initialSegmentId,false); + Constants.ServiceFactory.NetService.ProcessNode( endNodeId, (ushort nId, ref NetNode node) => { @@ -177,7 +178,7 @@ ref extSegEndMan.ExtSegmentEnds[ }); // Log._Debug($"SegmentTraverser: Traversal finished."); - return new List(visitedSegmentIds); + return visitedSegmentIds; } private static void TraverseRec(ref ExtSegment prevSeg, @@ -256,6 +257,7 @@ private static void TraverseRec(ref ExtSegment prevSeg, } visitedSegmentIds.Add(nextSegmentId); + // Log._Debug($"SegmentTraverser: Traversing segment {nextSegmentId}"); ushort nextStartNodeId = Constants.ServiceFactory.NetService.GetSegmentNodeId(nextSegmentId, true); diff --git a/TLM/TLM/Util/Shortcuts.cs b/TLM/TLM/Util/Shortcuts.cs index 04388c61a..204c148e1 100644 --- a/TLM/TLM/Util/Shortcuts.cs +++ b/TLM/TLM/Util/Shortcuts.cs @@ -43,6 +43,8 @@ internal static void Swap(this List list, int index1, int index2) { private static NetSegment[] _segBuffer => Singleton.instance.m_segments.m_buffer; + private static NetLane[] _laneBuffer => Singleton.instance.m_lanes.m_buffer; + private static ExtSegmentEnd[] _segEndBuff => segEndMan.ExtSegmentEnds; internal static IExtSegmentEndManager segEndMan => Constants.ManagerFactory.ExtSegmentEndManager; @@ -55,6 +57,8 @@ internal static void Swap(this List list, int index1, int index2) { internal static ref NetNode ToNode(this ushort nodeId) => ref GetNode(nodeId); + internal static ref NetLane ToLane(this uint laneId) => ref _laneBuffer[laneId]; + internal static ref NetSegment GetSeg(ushort segmentId) => ref _segBuffer[segmentId]; internal static ref NetSegment ToSegment(this ushort segmentId) => ref GetSeg(segmentId); @@ -103,10 +107,16 @@ internal static string CenterString(this string stringToCenter, int totalLength) return stringToCenter.PadLeft(leftPadding).PadRight(totalLength); } - internal static string ToSTR(this IEnumerable segmentList) { + /// + /// Creates and string of all items with enumerable inpute as {item1, item2, item3} + /// null argument returns "Null" + /// + internal static string ToSTR(this IEnumerable enumerable) { + if (enumerable == null) + return "Null"; string ret = "{ "; - foreach (T segmentId in segmentList) { - ret += $"{segmentId}, "; + foreach (T item in enumerable) { + ret += $"{item}, "; } ret.Remove(ret.Length - 2, 2); ret += " }";